Do I need a CASE expression? - sql

I have the following query which returns an organizations prior year (from the current year, so 2018) total wages.
SELECT
organization_id,
CASE
WHEN organization_id IN (SELECT text_1
FROM combo_table_detail
WHERE combo_table_id = 'wageAdjustment')
THEN SUM(ISNULL(component_value, 0)) + ISNULL(ctd.number_2, 0)
ELSE SUM(ISNULL(component_value, 0))
END AS "total_annual_wage",
MAX((begin_date)) AS "total_annual_wage_eff_date"
FROM
actual_pay_hours aph
LEFT JOIN
combo_table_detail ctd ON aph.organization_id = ctd.text_1
AND combo_table_id = 'wageAdjustment'
WHERE
organization_id = 'Org1'
AND component_name IN ('earnDef', 'earnings')
AND begin_date >= DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0)
AND begin_date < DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
GROUP BY
organization_id, ctd.number_2
However, I've run across an issue where some organizations either don't have a prior year (some only have 2019 wages), or their latest wages are from 2014. In both cases, the query returns blank values. This is due to the line
AND begin_date >= DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0)
AND begin_date < DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)
The expected result should look something like this:
+-----------------+-------------------+----------------------------+
| organization_id | total_annual_wage | total_annual_wage_eff_date |
+-----------------+-------------------+----------------------------+
| Org1 | 50000 | 12/1/2018 |
+-----------------+-------------------+----------------------------+
But instead, it looks like this:
+-----------------+-------------------+----------------------------+
| organization_id | total_annual_wage | total_annual_wage_eff_date |
+-----------------+-------------------+----------------------------+
This issue seems to be the fact that in the aph table, some units have wages for the 2018 year, while others don't. Example:
SELECT DISTINCT
YEAR(BEGIN_DATE) AS [Begin Date for Org1]
FROM
ACTUAL_PAY_HOURS aph
WHERE
ORGANIZATION_ID = 'Org1'
Results:
+---------------------+
| Begin Date for Org1 |
+---------------------+
| 1988 |
| 1989 |
| 1990 |
| 1991 |
| 1992 |
| 1993 |
| 1994 |
| 2004 |
| 2005 |
| 2006 |
| 2007 |
| 2008 |
| 2009 |
| 2010 |
| 2011 |
| 2012 |
| 2013 |
| 2014 |
+---------------------+
Additionally, the total wages for the prior year are then being adjusted by a value in the CTD (combo_table_detail) table. The issue is that when there are no values for an exisitng year, nothing is returned. What I need is for the wage total to then be 0 - since there isn't any data for that year, but then the value from the CTD table is added.
So, if Org1 has no wages for 2018, it should come out like this:
+-----------------+-------------+-------------------+-------+
| organization_id | total_wages | combo_table_value | total |
+-----------------+-------------+-------------------+-------+
| Org1 | 0 | 25000 | 25000 |
+-----------------+-------------+-------------------+-------+
So my question is, what logic can I add to this query that will return a result when the Organization doesn't have any prior year wages, but will still be added to the CTD table resulting in a value being returned?

Assuming that 2 of the fields in the WHERE clause belong to the joined table.
Try with moving those criteria to the LEFT JOIN.
SELECT aph.organization_id,
ISNULL(SUM(ctd.component_value),0) +
(CASE
WHEN SUM(ctd.component_value) IS NULL
THEN SUM (
SELECT d.component_value
FROM combo_table_detail d
WHERE d.combo_table_id = 'wageAdjustment'
AND d.text_1 = aph.organization_id
)
ELSE 0
END) AS [total_annual_wage],
MAX(aph.begin_date) AS [total_annual_wage_eff_date]
FROM actual_pay_hours AS aph
LEFT JOIN combo_table_detail AS ctd
ON ctd.text_1 = aph.organization_id
AND ctd.combo_table_id = 'wageAdjustment'
AND aph.component_name IN ('earnDef', 'earnings')
AND aph.begin_date >= DATEFROMPARTS(YEAR(GETDATE())-1,1,1)
AND aph.begin_date < DATEFROMPARTS(YEAR(GETDATE()),1,1)
WHERE aph.organization_id = 'orgID'
GROUP BY aph.organization_id, ctd.number_2

SELECT
aph.organization_id,
MAX(aph.begin_date) AS total_annual_wage_eff_date,
--earn components sum
ISNULL( SUM(aph.component_value), 0)
+
--adjustment sum, if any
(
SELECT ISNULL(SUM(ctd.number_2), 0)
FROM combo_table_detail ctd
WHERE ctd.text_1 = aph.organization_id
AND ctd.combo_table_id = 'wageAdjustment'
) AS total_annual_wage
FROM actual_pay_hours AS aph
WHERE aph.component_name IN ( 'earnDef', 'earnings' )
AND aph.begin_date >= DATEFROMPARTS(YEAR(GETDATE())-1, 1, 1)
AND aph.begin_date < DATEFROMPARTS(YEAR(GETDATE()), 1, 1)
GROUP BY aph.organization_id

Related

Select aggregate ignores where cause

I'm trying to transform an existing view into a format I can work with.
The view vw_temp_appHoursLastTwoEntries looks like this:
RowNumber | PersNr | Client | Localtion | Agent | Date | Calweek | Year
----------+--------+--------+-----------+-------+------------+---------+------
1 | 123 | 1 | 1 | ag-01 | 2020-01-01 | 1 | 2021
2 | 123 | 1 | 1 | ag-01 | 2020-01-03 | 1 | 2021
1 | 9999 | 1 | 4 | ag-01 | 2020-01-01 | 1 | 2021
2 | 9999 | 1 | 4 | ag-01 | 2020-01-07 | 1 | 2021
I need this data in a different format that would look like this:
PersNr | Client | Localtion | Agent | minDate | MaxDate | DateDiff | Calweek | Year
-------+--------+-----------+-------+------------+------------+----------+---------+-------
123 | 1 | 1 | ag-01 | 2020-01-01 | 2020-01-03 | 3 | 1 | 2021
9999 | 1 | 4 | ag-01 | 2020-01-01 | 2020-01-07 | 7 | 1 | 2021
in the original format, one person has only two rows (RowNumber 1 and 2). I'd like to match each column and have the min and max date as well as the difference in a new view.
my Code:
select a.persnr, a.client, a.location, a.agent, a.calweek, a.year,
max(a.date) as maxdate, min(b.date) as mindate
, DATEDIFF(day,a.date,b.date) as dDiff
from vw_temp_appHoursLastTwoEntries a
left join vw_temp_appHoursLastTwoEntries b on
a.persnr = b.persnr and a.client = b.client and
a.agent = b.agent and a.date = b.date
where a.date != b.date and DATEDIFF(day,a.date,b.date) != 0
or (a.date is not null and b.date is not null)
group by a.persnr, a.client, a.location, a.agent, a.calweek, a.year, DATEDIFF(day,a.date,b.date)
The issue:
I'm currently getting back values where it seems like the where cause does not take effect but I don't understand why.
a.date != b.date should not return rows where min- and maxdates are the same. The datediff does not return any other value then 0 even when the min- and maxdates are different.
Pretty sure this is what you want:
declare #Test table (RowNumber int, PersNr int, Client int, Localtion int, Agent varchar(5), [Date] date, Calweek int, [Year] int);
insert into #Test (RowNumber, PersNr, Client, Localtion, Agent, [Date], Calweek, [Year])
values
(1, 123, 1, 1, 'ag-01', '2020-01-01', 1, 2021),
(2, 123, 1, 1, 'ag-01', '2020-01-03', 1, 2021),
(1, 9999, 1, 4, 'ag-01', '2020-01-01', 1, 2021),
(2, 9999, 1, 4, 'ag-01', '2020-01-07', 1, 2021);
select a.PersNr, a.Client, a.Localtion, a.Agent, a.Calweek, a.[Year]
, max(a.[date]) as maxdate
, min(b.[date]) as mindate
, abs(datediff(day,a.[date],b.[date])) as dDiff
from #Test a
left join #Test b on
a.persnr = b.persnr and a.client = b.client and
a.agent = b.agent --and a.[date] = b.[date]
where (/*a.[date] != b.[date] and*/ datediff(day,a.[date],b.[date]) != 0)
and /* not OR */ (a.[date] is not null and b.[date] is not null)
group by a.persnr, a.client, a.Localtion, a.agent, a.calweek, a.[Year], abs(datediff(day,a.[date],b.[date]));
Returns:
PersNr
Client
Localtion
Agent
Calweek
Year
maxdate
mindate
dDiff
123
1
1
ag-01
1
2021
2020-01-03
2020-01-01
2
9999
1
4
ag-01
1
2021
2020-01-07
2020-01-01
6
As Giorgos points out, you don't want to join on a.[date] = b.[date] because your where clause specifically filters that condition out.
The main issue was using OR instead of AND, you want to ensure that both date values are not null so that is an AND condition.
I am also assuming that dDiff is for debugging purposes only, which as you have it kept the rows from grouping, but you can group them by using the absolute value (abs).
You also don't need to test a.[date] != b.[date] because that is already true by virtue of datediff(day,a.[date],b.[date]) != 0.
Please use this form of DDL+DML (or a temp table) in future to provide sample data for us to work with (it gives you a minimal reproducible example also which is never a bad thing, because I picked up a number of typos in your query while copying it).

How to perform group by in SQL Server for specific output

I have a table with few records, I want to get month wise data along with count on one of the column. The output should contain Month and count of Isregistered flag.
Table structure
| Inserted On | IsRegistered |
+-------------+--------------+
| 10-01-2020 | 1 |
| 15-01-2020 | 1 |
| 17-01-2020 | null |
| 17-02-2020 | 1 |
| 21-02-2020 | null |
| 04-04-2020 | null |
| 18-04-2020 | null |
| 19-04-2020 | 1 |
Excepted output
| Inserted On | Registered | Not Registered
+-------------+------------+---------------
| Jan | 2 | 1
| Feb | 1 | 1
| Apr | 1 | 2
I tried by performing normal group by but didn't got desired output
SELECT
DATENAME(MONTH, dateinserted) AS [MonthName], COUNT(ISRegistered)
FROM
tablename
GROUP BY
(DATENAME(MONTH, dateinserted))
Note: here null is treated as not registered
You can use aggregation. I would include the year and use the month number rather than name, so:
select year(inserted_on), month(inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by year(inserted_on), month(inserted_on)
order by year(inserted_on), month(inserted_on);
Note: If you really want the monthname and want to combine data from different years (which seems unlikely, but . . . ), then you can use:
select datename(month, inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by datename(month, inserted_on)
order by month(min(inserted_on));
The GROUP BY should include both the year and month (so there's no overlapping) as well as the DATENAME (for display). Something like this
drop table if exists #tablename;
go
create table #tablename(dateinserted date, ISRegistered int);
insert #tablename values
('2020-12-01', 0),
('2020-11-02', 1),
('2020-11-03', 1),
('2020-12-01', 1),
('2020-12-03', 1),
('2020-11-02', 0);
select year(dateinserted) yr,
datename(month, dateinserted) AS [MonthName],
sum(ISRegistered) Registered ,
sum(1-ISRegistered) [Not Registered]
from #tablename
group by year(dateinserted), month(dateinserted), datename(month, dateinserted)
order by year(dateinserted), month(dateinserted);
yr MonthName Registered Not Registered
2020 November 2 1
2020 December 2 1

Using t-sql to lookup value based on dates in other table

I have the following challenge. I have 2 tables. First table contains changes in values of bikes, at a certain moment (i.e. price catalogue). This means a certain price for a product is valid untl there is a new price within the table.
Product | RowNr | Year | Month | Value
------------------------------------------
Bike1 | 1 | 2009 | 8 | 100
Bike1 | 2 | 2010 | 2 | 400
Bike1 | 3 | 2011 | 4 | 300
Bike1 | 4 | 2012 | 9 | 100
Bike1 | 5 | 2013 | 2 | 500
Bike1 | 6 | 2013 | 5 | 200
Bike2 | 1 | 2013 | 1 | 5000
Bike2 | 2 | 2013 | 2 | 4000
Bike2 | 3 | 2014 | 6 | 2000
Bike2 | 4 | 2014 | 10 | 4000
The second table contains dates for which I would like to determine the value of a bike (based on the information in table 1).
Product | Date | Value
-------------------------
Bike1 | 3/01/2008 | ?
Bike1 | 04/30/2011 | ?
Bike1 | 5/08/2009 | ?
Bike1 | 10/10/2012 | ?
Bike1 | 7/01/2014 | ?
So line 1 and 3 should get value "400", line 2 "300", line 4 "100" and line 5 "200" etc.
Does anyone know how this can be achieved in T-SQL? I've already partitioned the first table, but could use some advice on the next steps.
Many thanks,
You could do something like this, which will retrieve the most recent price catalogue value for the product, using the price that is less than or equal to the product table date.
SELECT p.product
, p.date
, valueAsOfDate =
( SELECT TOP 1 c.value
FROM priceCatalogue c
WHERE c.product = p.product
AND convert(date,
convert(varchar(4), c.year) + '-'
+ convert(varchar(2), c.month)
+ '-1'
) <= p.date
--this order by will ensure that the most recent price is used
ORDER BY c.year desc, c.month desc
)
FROM product p
This table structure is not ideal... you would be better off with an "AsOfDate" column in your priceCatalogue table, so that you do not have to cast the values in the priceCatalogue table as a date in order to compare. If this is new development, change the priceCatalogue table to have an asOfDate column that is a date data type. If this is an existing table that is populated from another data source, then you could look at adding a persisted computed column to the table. http://msdn.microsoft.com/en-us/library/ms188300.aspx
With asOfDate column on the productCatalogue table, you have a SARG-able query (What makes a SQL statement sargable? ) that can take advantage of indexes.
SELECT p.product
, p.date
, valueAsOfDate =
( SELECT TOP 1 c.value
FROM priceCatalogue c
WHERE c.product = p.product
AND c.asOfDate <= p.date
--this order by will ensure that the most recent price is used
ORDER BY c.year desc, c.month desc
)
FROM product p
just use the YEAR() and MONTH() functions to take those parts of the date, and join them on your versioned table.
select
from product p
inner join productVersion pv
on p.product = pv.product
and Year(p.Date) = pv.Year
and Month(p.Date) = pv.Month
Xivan,
I think for both your line 1 and 3 it should get value "100" as 3/1/2008 and 5/8/2009 is less then 8/xx/2009.
As your table structure is not ideal, you have to create some computed columns for calculation.Hope the below query will work for you.
WITH cte
AS (
SELECT p.*
,(
SELECT min(p1.rownr) rownr
FROM product p1
WHERE p1.rownr > p.rownr
AND p.product = p1.product
GROUP BY p1.product
) AS nrownr
,(
SELECT max(p1.rownr) rownr
FROM product p1
WHERE p1.rownr < p.rownr
AND p.product = p1.product
GROUP BY p1.product
) AS prownr
FROM product p
)
SELECT pd.*
,c.value
FROM product_date pd
LEFT JOIN cte c ON pd.product = c.product
LEFT JOIN product p ON c.product = p.product
AND c.nrownr = p.rownr
LEFT JOIN product p1 ON c.product = p1.product
AND c.prownr = p1.rownr
WHERE (pd.DATE !> convert(DATE, convert(VARCHAR(4), (
CASE WHEN p.year IS NOT NULL THEN p.year ELSE 9999 END)) + '-' + convert(VARCHAR(2), (
CASE WHEN p.month IS NOT NULL THEN p.month ELSE 12 END)) + '-' + '1')
AND
pd.DATE !< convert(DATE, convert(VARCHAR(4), c.year) + '-' + convert(VARCHAR(2), c.month) + '-' + '1'))
OR
(pd.DATE !> convert(DATE, convert(VARCHAR(4), (
CASE WHEN p1.year IS NOT NULL THEN NULL ELSE 2009 END)) + '-' +
convert(VARCHAR(2), ( CASE WHEN p1.month IS NOT NULL THEN NULL ELSE 8 END)) + '-' +'1')
)
http://sqlfiddle.com/#!3/22c1d/2

filter by Sum without Grouping

i have a resultset that i generate from a query that Looks like this:
Select Employee, Month, (select case when Status = '---' then 0 Else 1 end) as PlaningValue
From PlanningTable PT
Where Month >= #From Month and Month <= #ToMonth
The Result of this Looks something like this:
|Employee| Month | PlaningValue |
|George | 2014-01 | 1 |
|George | 2014-02 | 1 |
|George | 2014-03 | 0 |
|Andrew | 2014-01 | 0 |
|Andrew | 2014-02 | 1 |
|Andrew | 2014-03 | 0 |
|Howard | 2014-01 | 1 |
|Howard | 2014-02 | 1 |
|Howard | 2014-03 | 1 |
Now what i want is the following:
Filter out Employee's who, over the three month period, have a total planing Value of 3,
in the example above, Howard would be filtered out.
Is there a way to do this nicely or is it all just impossible to even thin ?
(Remark: Since i am going to use the Query on Reporting Services, i can't use the OVER function)
Thank you all for your help
This looks to be SQL Server syntax, as such I you can use windowed functions:
WITH CTE AS
( SELECT Employee,
Month,
PlanningValue = CASE WHEN Status = '---' THEN 0 ELSE 1 END,
Total = SUM(CASE WHEN Status = '---' THEN 0 ELSE 1 END)
OVER (PARTITION BY Employee)
FROM PlanningTable
WHERE Month >= #FromDate
AND Month <= #ToMonth
)
SELECT Employee, Month, PlanningValue
FROM CTE
WHERE Total != 3;
Simplified Example on SQL Fiddle
Try:
select pt.employee, pt.month, pt.planningvalue
from planningtable pt
join planningtable pt2 on pt.employee = pt2.employee
join planningtable pt3 on pt.employee = pt3.employee
join planningtable pt4 on pt.employee = pt4.employee
where month >= #mofrom and month <= #tomonth
and pt2.month = #tomonth
and pt3.month in (select month from planningtable where month > #mofrom and month < #tomonth)
and pt4.month = #mofrom
and pt2.planningvalue + pt3.planningvalue + pt4.planningvalue <> 3

How do you select from a date range as the data source

Short of creating a table with all of the values of a date range, how would I select from a datarange as a datasource.
What I'm trying to accomplish is to create a running total of all items created within the same week from separate tables, while showing weeks with 0 new
example table:
items
-----------------------------
created_on | name | type
-----------------------------
2012-01-01 | Cards | 1
2012-01-09 | Red Pen | 2
2012-01-31 | Pencil | 2
2012-02-01 | Blue Pen | 2
types
--------------
name | id
--------------
Fun | 1
Writing | 2
sample output:
----------------------------
year | week | fun | writing
----------------------------
2012 | 1 | 1 | 0
2012 | 2 | 0 | 1
2012 | 3 | 0 | 0
2012 | 4 | 0 | 0
2012 | 5 | 0 | 2
You could generate a number series for the week numbers
SELECT
w.week
FROM
(SELECT generate_series(1,52) as week) as w
Example
SELECT
w.year,
w.week,
COUNT(i1) as fun,
COUNT(i2) as writing
FROM (SELECT 2012 as year, generate_series(1,6) as week) as w
LEFT JOIN items i1 ON i1.type = 1 AND w.week = EXTRACT(WEEK FROM i1.created_on)
LEFT JOIN items i2 ON i2.type = 2 AND w.week = EXTRACT(WEEK FROM i2.created_on)
GROUP BY
w.year,
w.week
ORDER BY
w.year,
w.week
Very close erikxiv, but you got me in the right direction. I have multiple tables I need to grab information from, this the additional select in the select fields.
select
date_year.num,
date_week.num,
( select count(*) from items x
and EXTRACT(YEAR FROM x.created_on) = date_year.num
and EXTRACT(WEEK FROM x.created_on) = date_week.num
) as item_count
from
(SELECT generate_series(2011, date_part('year', CURRENT_DATE)::INTEGER) as num) as date_year,
(SELECT generate_series(1,52) as num) as date_week
where
(
date_year.num < EXTRACT (YEAR FROM CURRENT_DATE)
OR
(
date_year.num = EXTRACT (YEAR FROM CURRENT_DATE) AND
date_week.num <= EXTRACT (WEEK FROM CURRENT_DATE)
)
)