Transform column into rows in SQL Server table - sql

I have a query in which I want some columns to be appear as rows.
The query is
Select *
From Emp_mon_day
Where emp_mkey IN (select emp_card_no
from emp_mst
where comp_mkey in (7, 110))
and Year = 2016 and month = 2
and Emp_mkey = 2492
with this output being returned:
Now, I need to show columns Day1, Day2, Day3 as rows in the output with the above query.
How to achieve that?

You can use case an UNPIVOT query like below
Select
comp_mkey,
fmodule_id,
fdepartment_id,
branch_mkey,
entry_department,
dept_mkey,
mkey,
emp_mkey,
entry_date,
month,
year,
day,
data
from
(select * from Emp_mon_day where emp_mkey IN
(select emp_card_no from emp_mst where comp_mkey in
(7,110)) and Year = 2016 and month = 2
and Emp_mkey = 2492) s
unpivot
(
data for day in ([Day1],[Day2]) -- dynamic query can generate all days data
)up
Below is sample test script and output
create table t(comp_mkey int,mont int,yea int,day1 varchar(10),day2 varchar(10))
insert into t values (2,2,2016,'AB','AC')
Select
comp_mkey,
mont,
yea,
day,
data
from
(select * from t) s
unpivot
(
data for day in ([Day1],[Day2]) -- dynamic query can generate all days data
)up
drop table t
Output
If you need all day's data you can either type out all expected columns in this statement
data for day in ([Day1],[Day2], [Day3],[Day4])
Better way would be to convert this into a dynamic query and apply logic for number of days expected in a month

CROSS APPLY can be proven helpful here.
CREATE TABLE [dbo].[emp]
(
[comp_mkey] [INT] NULL,
[mont] [INT] NULL,
[year] [INT] NULL,
[day1] [VARCHAR](10) NULL,
[day2] [VARCHAR](10) NULL
)
INSERT INTO emp VALUES (2, 2, 2016, 'AB', 'AC')
Use following Select statement
SELECT emp.comp_mkey
, emp.mont
, emp.year
, emp_ext.[Day]
, emp_ext.Value
FROM emp
CROSS APPLY
(
VALUES('Day1', emp.day1), ('Day2', emp.day2)
)emp_ext([Day], Value)

Related

sql subtraction on two different tables

I have two different tables, common column is truck_id.
I need to subtract two tables from each other to find the net amount.
The result I want:
truck_id
difference
35kd85
1500
35hh52
900
SELECT
(SELECT SUM(last_revenue) FROM (
SELECT DISTINCT last_revenue FROM
Expedition WHERE YEAR(departure_date) > 2020 AND truck_id = '31adc444'
UNION ALL SELECT last_revenue FROM
ChainingExpedition WHERE YEAR(departure_date) > 2020 AND truck_id = '31adc444'
)x
)-(SELECT SUM(price_dollar) FROM (
SELECT DISTINCT price_dollar FROM TruckMaintenanceExpense
WHERE YEAR(payment_date) > 2020 AND expense_type = 'çeker'
AND truck_id ='31adc444'
)x
) AS difference
SQL subtraction on two different tables
When I type truck_id in my query, I get the right result, but my goal is to draw as a list.
Typically, I would create a list of all trucks that meet the criteria you are looking for. Then, I'll get a list of all trucks with revenue and a separate list of trucks with expense. Then you join those 3 tables together and do the math. You query was very hard to follow without good indenting and structure. Next time you ask a question, be sure to include sample data. You should be writing all the CREATE TABLE and INSERT INTO statements that I include in the EXAMPLE DATA section in the fiddle below.
--*****EXAMPLE DATA*****
CREATE TABLE Expedition (
truck_id nvarchar(50)
, last_revenue decimal(19,2)
, departure_date datetime
);
CREATE TABLE TruckMaintenanceExpense (
truck_id nvarchar(50)
, price_dollar decimal(19,2)
, payment_date datetime
);
INSERT INTO Expedition (truck_id, last_revenue, departure_date)
VALUES ('35kd85', 2000.00, '2020-1-1')
, ('35kd85', 3500.00, '2020-1-1')
, ('35hh52', 300.00, '2020-1-1')
, ('35hh52', 258.98, '2020-1-1')
;
INSERT INTO TruckMaintenanceExpense (truck_id, price_dollar, payment_date)
VALUES ('35kd85', 9865.23, '2020-2-1')
, ('35kd85', 321.54, '2020-2-1')
, ('35hh52', 159.78, '2020-2-1')
, ('35hh52', 598.77, '2020-2-1')
;
--*****END EXAMPLE DATA*****
--Create a list of all truck_ids. It would be more helpful
--if you had a table that defined all the trucks (i.e. dbo.trucks).
WITH AllTrucks as (
SELECT truck_id
FROM TruckMaintenanceExpense
UNION ALL
SELECT truck_id
FROM Expedition
)
SELECT DISTINCT
a.truck_id
--Use ISNULL to make sure we have 0.00 if the truck is missing
--either revenue or expense.
, ISNULL(tRev.total_revenue,0) - ISNULL(tExp.total_expense,0) as difference
--Get the list of truck_ids. Use SELECT DISTINCT to elimiate duplicates.
FROM AllTrucks as a
--Get a list of all trucks with Expedition reveue.
LEFT OUTER JOIN (
SELECT
e.truck_id
, YEAR(e.departure_date) as [year]
, SUM(e.last_revenue) as total_revenue
FROM Expedition as e
WHERE e.departure_date >= '2020-1-1 00:00:00'
AND e.departure_date < '2021-1-1 00:00:00'
GROUP BY e.truck_id, YEAR(departure_date)
) as tRev
ON tRev.truck_id = a.truck_id
--Get a list of all trucks with maintenance expenses.
LEFT OUTER JOIN (
SELECT
tme.truck_id
, YEAR(tme.payment_date) as [year]
, SUM(tme.price_dollar) as total_expense
FROM TruckMaintenanceExpense as tme
WHERE tme.payment_date >= '2020-1-1 00:00:00'
AND tme.payment_date < '2021-1-1 00:00:00'
GROUP BY tme.truck_id, YEAR(payment_date)
) as tExp
ON tExp.truck_id = a.truck_id
;
truck_id
difference
35hh52
-199.57
35kd85
-4686.77
fiddle

UNPIVOT Holiday Hours

I have a table, that keeps track of store holiday hours:
LOCATION_ID DATE1 TIMES1 DATE2 TIMES2
123456 2020-12-12 10:00AM-09:00PM 2020-12-19 10:00AM-09:00PM
This is a highly oversimplified table. There's about 30 columns horzontially consisting of store operating hours by date - It continues (DATE3, TIMES3, DATE4, TIMES4, etc).
I need to unpivot the values vertically, ensuring the date and time values are on the same record.
(NOTE: Once I figure out to structure the UNPIVOT expression properly, I will use Dynamic SQL on my own to pivot the column names)
Desired Outcome:
LOCATION_ID DATE TIME
123456 2020-12-12 10:00AM-09:00PM
123456 2020-12-19 10:00AM-09:00PM
I tried using UNPIVOT, but I'm stuck. Any ideas?
SAMPLE DATA:
CREATE TABLE #HOURS (LOCATION_ID int, DATE1 varchar(255), TIMES1 varchar(255), DATE2
varchar(255), TIMES2 varchar(255));
INSERT INTO #HOURS VALUES ('123456', '2020-12-12', '10:00AM-09:00PM','2020-12-19','10:00AM-09:00PM' )
Code that I tried:
SELECT *
FROM (SELECT location_id,
[date1],
[times1],
[date2]
FROM #hours) AS cp
UNPIVOT ( pivotvalues
FOR pivvalues IN ([Date1],
[date2],
[times1]) ) AS up1
Gordon is 100% correct (+1).
However, if you are looking for a dynamic approach WITHOUT having to use Dynamic SQL, consider the following.
Example
Select Location_ID
,Date = max(case when [Item] like 'DATE%' then Value end)
,Time = max(case when [Item] like 'TIME%' then Value end)
From (
select A.Location_ID
,Grp = replace(replace([Item],'DATE',''),'TIMES','')
,B.*
from #hours A
Cross Apply [dbo].[tvf-XML-Unpivot-Row]( (Select A.* for XML RAW) ) B
Where [Item] not in ('LOCATION_ID')
) A
Group By Location_ID,Grp
Returns
Location_ID Date Time
123456 2020-12-12 10:00AM-09:00PM
123456 2020-12-19 10:00AM-09:00PM
The Table-Valued Function if Interested
CREATE FUNCTION [dbo].[tvf-XML-UnPivot-Row](#XML xml)
Returns Table
As
Return (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From #XML.nodes('//#*') xNode(xAttr)
)
Don't use unpivot. Use apply:
select h.location_id, v.date, v.time
from #hours h cross apply
(values (h.date1, h.times1), (h.date2, h.times2)
) v(date, time);
unpivot is non-standard syntax that does exactly one thing. APPLY is the SQL Server implementation of lateral joins. This is a very powerful join type -- using it for unpivoting is a good way to start learning the syntax.

Determining consecutive and independent PTO days

Based on feedback, I am restructuring my question.
I am working with SQL on a Presto database.
My objective is to report on employees that take consecutive days of PTO or Sick Time since the beginning of 2018. My desired output would have the individual islands of time taken by employee with the start and end dates, along the lines of:
The main table I am using is d_employee_time_off
There are only two time_off_type_name: PTO and Sick Leave.
The ds is a datestamp and I use the latest ds (usually the current date)
I have access to a date table named d_date
I can join the tables on d_employee_time_off.time_off_date = d_date.full_date
I hope that I have structured this question in a fashion that is understandable.
I believe the need here is to join the day off material to a calendar table.
In the example solution below I am generating this "on the fly" but I think you do have your own solution for this. Also in my example I have used the string 'Monday' and moved backward from that (or, you could use 'Friday' and move forward). I'm, not keen on language dependent solutions but as I'm not a Presto user wasn't able to test anything on Presto. So the example below uses some of your own logic, but using SQL Server syntax which I trust you can translate to Presto:
Query:
;WITH
Digits AS (
SELECT 0 AS digit UNION ALL
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9
)
, cal AS (
SELECT
ca.number
, dateadd(day,ca.number,'20180101') as cal_date
, datename(weekday,dateadd(day,ca.number,'20180101')) weekday
FROM Digits [1s]
CROSS JOIN Digits [10s]
CROSS JOIN Digits [100s] /* add more like this as needed */
cross apply (
SELECT
[1s].digit
+ [10s].digit * 10
+ [100s].digit * 100 /* add more like this as needed */
AS number
) ca
)
, time_off AS (
select
*
from cal
inner join mytable t on (cal.cal_date = t.time_off_date and cal.weekday <> 'Monday')
or (cal.cal_date between dateadd(day,-2,t.time_off_date)
and t.time_off_date and datename(weekday,t.time_off_date) = 'Monday')
)
, starting_points AS (
SELECT
employee_id,
cal_date,
dense_rank() OVER(partition by employee_id
ORDER BY
time_off_date
) AS rownum
FROM
time_off A
WHERE
NOT EXISTS (
SELECT
*
FROM
time_off B
WHERE
B.employee_id = A.employee_id
AND B.cal_date = DATEADD(day, -1, A.cal_date)
)
)
, ending_points AS (
SELECT
employee_id,
cal_date,
dense_rank() OVER(partition by employee_id
ORDER BY
time_off_date
) AS rownum
FROM
time_off A
WHERE
NOT EXISTS (
SELECT
*
FROM
time_off B
WHERE
B.employee_id = A.employee_id
AND B.cal_date = DATEADD(day, 1, A.cal_date)
)
)
SELECT
S.employee_id,
S.cal_date AS start_range,
E.cal_date AS end_range
FROM
starting_points S
JOIN
ending_points E
ON E.employee_id = S.employee_id
AND E.rownum = S.rownum
order by employee_id
, start_range
Result:
employee_id start_range end_range
1 200035 02.01.2018 02.01.2018
2 200035 20.04.2018 27.04.2018
3 200037 27.01.2018 29.01.2018
4 200037 31.03.2018 02.04.2018
see: http://rextester.com/MISZ50793
CREATE TABLE mytable(
ID INT NOT NULL
,employee_id INTEGER NOT NULL
,type VARCHAR(3) NOT NULL
,time_off_date DATE NOT NULL
,time_off_in_days INT NOT NULL
);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (1,200035,'PTO','2018-01-02',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (2,200035,'PTO','2018-04-20',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (3,200035,'PTO','2018-04-23',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (4,200035,'PTO','2018-04-24',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (5,200035,'PTO','2018-04-25',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (6,200035,'PTO','2018-04-26',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (7,200035,'PTO','2018-04-27',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (8,200037,'PTO','2018-01-29',1);
INSERT INTO mytable(id,employee_id,type,time_off_date,time_off_in_days) VALUES (9,200037,'PTO','2018-04-02',1);

Cross join to get all quarter end months for specific month in particular year

I tried to code logic to get below output but with no success. My mind
now running blank. Can you share with me your ideas ? I am sure together we
could figure out some of the simplest solutions in SQL to get the below
output.
Adding more, I do not want Q4 keys.
Please note, the table is a Month dimension containing few more columns such a
Prev01, 02 ... month keys.
Tagged with SQL Server & Teradata as well as I am concerned with the logic only & not the syntax. I am using Teradata.
Existing data:
Expected output:
So total expected records for a year would be 12*3 = 36.
EDIT : For every year, we have estimate & budget values. For ex.
For 2016, the estimate values will come from March-2016 (Q1), June-2016 (Q2), September-2016 (Q3). And one budget value that will be from December-2015 (Q4).
So any month from 2016 would have the same budget-estimate values. This will change only if the year changes. I hope it is clear now ...
Unfortunately I would not be able to share the table DDLs or sample data. So tried to explain the scenario as simple as possible. I am able to code SQL to get the budget values, but for the estimates, scenario is bit tricky. So lets say for April-2016, the estimate values I want are March-2016, June-2016, September-2016.
Existing data is a screenshot similar to the sample data.
Adding more, month key is like an identity column. Jan-2017 would have month key as 13 & so on ...
here it is
select t1.month_key, t1.Month, Year, t3.Month+'-'+right(CAST(Year as varchar(4)),2) Scenario, t2.month_key Quarter_End
from YourData t1
cross join (
select Quarter_Start, MAX(month_key) month_key
from YourData
group by Quarter_Start
) t2
join (
select month_key, Month
from YourData
) t3 on t2.month_key = t3.month_key
where t2.month_key<>12 -- if you do not want last quarter
order by 1,5
In MSSQL , we can do as follows:
declare #temp table (month_key int,Months varchar(100),Years int,Quarter_Start int)
insert into #temp values (1 ,'Jan',2016, 1 )
insert into #temp values (2 ,'Feb',2016, 1 )
insert into #temp values (3 ,'Mar',2016, 1 )
insert into #temp values (4 ,'Apr',2016, 2 )
insert into #temp values (5 ,'May',2016, 2 )
insert into #temp values (6 ,'Jun',2016, 2 )
insert into #temp values (7 ,'Jul',2016, 3 )
insert into #temp values (8 ,'Aug',2016, 3 )
insert into #temp values (9 ,'Sep',2016, 3 )
insert into #temp values (10,'Oct',2016, 4 )
insert into #temp values (11,'Nov',2016, 4)
insert into #temp values (12,'Dec',2016, 4)
select 1 as 'Month_Key','Jan' as 'Month',Years,Months+'-16' as 'Scenario',month_key as 'Quarter End' from #temp where month_key in
(select max(month_key) from #temp group by Quarter_Start)

Grouping by timeframes with a modifier that changes over time

After poring over a similar problem and finding it never provided a complete solution I finally have gotten to the heart of the problem I can't solve. I'm looking for the consecutive amount of days that a person can be prescribed a certain amount of drugs. Because the prescriptions begin and end, there can be multiple, non-contiguous intervals that a person is on X number of drugs. The following SQL script produces the result set of the query I'll post momentarily: Also, I don't have SQL Server 2012.
create table test
(pat_id int, cal_date date, grp_nbr int, drug_qty int,[ranking] int)
go
insert into test(pat_id,cal_date, grp_nbr,drug_qty,[ranking])
values
(1, '1/8/2007',7,2, 1),
(1, '1/9/2007',7,2, 1),
(1, '1/10/2007',7, 2,1),
(1, '1/11/2007',7, 2,1),
(1, '1/12/2007',7, 2,1),
(1, '1/13/2007',7, 2,1),
(1, '1/14/2007',7, 2,1),
(1, '1/15/2007',7, 2,1),
(1, '6/1/2007',7,2, 1),
(1, '6/2/2007',7,2, 1),
(1, '6/3/2007',7,2, 1)
Notice here that there are two non-contiguous intervals where this person was on two drugs at once. In the days that are omitted,drug_qty was more than two. The last column in this example was my attempt at adding another field that I could group by to help solve the problem (didn't work).
Query to create tables:
CREATE TABLE [dbo].[rx](
[pat_id] [int] NOT NULL,
[fill_Date] [date] NOT NULL,
[script_End_Date] AS (dateadd(day,[dayssup],[filldate])),
[drug_Name] [varchar](50) NULL,
[days_Sup] [int] NOT NULL,
[quantity] [float] NOT NULL,
[drug_Class] [char](3) NOT NULL,
CHECK(fill_Date <=script_End_Date
PRIMARY KEY CLUSTERED
(
[clmid] ASC
)
CREATE TABLE [dbo].[Calendar](
[cal_date] [date] PRIMARY KEY,
[Year] AS YEAR(cal_date) PERSISTED,
[Month] AS MONTH(cal_date) PERSISTED,
[Day] AS DAY(cal_date) PERSISTED,
[julian_seq] AS 1+DATEDIFF(DD, CONVERT(DATE, CONVERT(varchar,YEAR(cal_date))+'0101'),cal_date),
id int identity);
the query I'm using to produce my result sets:
;WITH x
AS (SELECT rx.pat_id,
c.cal_date,
Count(DISTINCT rx.drug_name) AS distinctDrugs
FROM rx,
calendar AS c
WHERE c.cal_date BETWEEN rx.fill_date AND rx.script_end_date
AND rx.ofinterest = 1
GROUP BY rx.pat_id,
c.cal_date
--the query example I used having count(1) =2, but to illustrate the non-contiguous intervals, in practice I need the below having statement
HAVING Count(*) > 1),
y
AS (SELECT x.pat_id,
x.cal_date
--c2.id is the row number in the calendar table.
,
c2.id - Row_number()
OVER(
partition BY x.pat_id
ORDER BY x.cal_date) AS grp_nbr,
distinctdrugs
FROM x,
calendar AS c2
WHERE c2.cal_date = x.cal_date)
SELECT *,
Rank()
OVER(
partition BY pat_id, grp_nbr
ORDER BY distinctdrugs) AS [ranking]
FROM y
WHERE y.pat_id = 1604012867
AND distinctdrugs = 2
Besides the fact that I shouldn't have a column in the calendar table named 'id', is there anything egregiously wrong with this approach? I can get the query to show me the distinct intervals of distinctDrugs=x, but it will only work for that integer and not anything >1. By this I mean that I can find the separate intervals where a patient is on two drugs, but only when I use =2 in the having clause, not >1. I can't do something like
SELECT pat_id,
Min(cal_date),
Max(cal_date),
distinctdrugs
FROM y
GROUP BY pat_id,
grp_nbr
because this will pick up that second group of non-contiguous dates. Does anyone know of an elegant solution to this problem?
The key to this is a simple observation. If you have a sequence of dates, then the difference between them and an increasing sequence is constant. The following does this, assuming you are using SQL Server 2005 or greater:
select pat_id, MIN(cal_date), MAX(cal_date), MIN(drug_qty)
from (select t.*,
cast(cal_date as datetime) - ROW_NUMBER() over (partition by pat_id, drug_qty order by cal_date) as grouping
from #test t
) t
group by pat_id, grouping