I have a table punches that looks like this
EMP_ID INpunchDATETIME OUTpunchDATETIME
-----------------------------------------------
1 2017-11-10 11:59 2017-11-10 13:30
1 2017-11-10 9:00 2017-11-10 10:30
I need to create a table #temptable from the previous table that looks like this
Emp_ID InPunch1 InPunch2 OUTpunch1 OUTpunch2
----------------------------------------------------------------------------
1 2017-11-10 9:00 2017-11-10 11:59 2017-11-10 10:30 2017-11-10 13:30
I'm trying to use PIVOT but if that's wrong I can change
DECLARE #temptable Table (
EMP_ID int,
InPunch1 datetime,
InPunch2 datetime,
OutPunch1 datetime,
OutPunch2 datetime);
SELECT
Emp_ID, InPunch1, InPunch2, Outpunch1, Outpunch2
INTO
#temptable
FROM
(SELECT
EMP_ID, INPunchDATETIME, OUTpunchDATETIME
FROM
punches) AS p
PIVOT
(
That's as far as I've got.
Sample Data Setup
create table dbo.punches
(
emp_id int
, INpunchDATETIME datetime
, OUTpunchDATETIME datetime
)
insert into dbo.punches
values (1, '2017-11-10 11:59','2017-11-10 13:30')
, (1, '2017-11-10 9:00','2017-11-10 10:30')
Answer
The punches tables has the in/out punches in two separate column, and the inner most query moves both types of punches into one column to allow all of the data to be pivoted at once. The next query puts them in chronological order, and creates values in punch_ind that will be the eventual column names. Last step is to pivot the data and select the final output.
select post.emp_id
, post.InPunch1
, post.InPunch2
, post.OutPunch1
, post.OutPunch2
from (
--decide which punch is in1/in2/etc.
select sub.emp_id
, sub.punch_type + 'Punch' + cast(row_number() over (partition by sub.emp_id, sub.punch_type order by sub.punch_ts) as varchar(10)) as punch_ind --punch indicator
, sub.punch_ts
from (
--get all of the data in one column to enable pivot
select p.emp_id
, 'In' as punch_type
, p.INpunchDATETIME as punch_ts
from dbo.punches as p
union all
select p.emp_id
, 'Out' as punch_type
, p.OUTpunchDATETIME as punch_ts
from dbo.punches as p
) as sub
) as pre --before the pivot
pivot (max(pre.punch_ts) for pre.punch_ind in ([InPunch1], [InPunch2], [OutPunch1], [OutPunch2])) as post --after the pivot
Just take this final output and insert the records into the temp table/table variable of your choice.
Related
I have a very simple table like below
Events:
Event_name
Event_time
A
2022-02-10
B
2022-05-11
C
2022-07-17
D
2022-10-20
To a table like this are added new events, but we always take the event from the last X days (for example, 30 days), so the query result for this table is changeable.
I would like to transform the above table into this:
A
B
C
D
2022-02-10
2022-05-11
2022-07-17
2022-10-20
In general, the number of columns won't be constant. But if it's not possible we can add a limitation for the number of columns- for example, 10 columns.
I tried with crosstab, but I had to add the column name manually this is not what I mean and it doesn't work with the CTE query
WITH CTE AS (
SELECT DISTINCT
1 AS "Id",
event_time,
event_name,
ROW_NUMBER() OVER(ORDER BY event_time) AS nr
FROM
events
WHERE
event_time >= CURRENT_DATE - INTERVAL '31 days')
SELECT *
FROM
crosstab (
'SELECT id, event_name, event_time
FROM
CTE
WHERE
nr <= 10
ORDER BY
nr') AS ct(id int,
event_name text,
EventTime1 timestamp,
EventTime2 timestamp,
EventTime3 timestamp,
EventTime4 timestamp,
EventTime5 timestamp,
EventTime6 timestamp,
EventTime7 timestamp,
EventTime8 timestamp,
EventTime9 timestamp,
EventTime10 timestamp)
This query will be used as the data source in Tableau (data visualization and analysis software) it would be great if it could be one query (without temp tables, adding new functions, etc.)
Thanks!
I want to display Day name based on ID which is in bigint[] in the table as shown below:
Table:
create table tbl_days
(
day_ids bigint[]
);
Records:
insert into tbl_days values('{1,2}');
insert into tbl_days values('{1,2,3}');
insert into tbl_days values('{1,4}');
insert into tbl_days values('{4,7}');
insert into tbl_days values('{1,2,3,4,5,6,7}');
insert into tbl_days values('{2,4,7}');
Would like to display day name for:
1 for Monday
2 for Tuesday
.
..
7 for Sunday.
Query 1: Using replace(), which is taking 3 more seconds to get the main query result.
select replace(replace(replace(replace(replace(replace(replace(day_ids::varchar,'1','Monday'),'2','Tuesday'),'3','Wednesday'),'4','Thursday'),'5','Friday'),'6','Saturday'),'7','Sunday')
from tbl_days;
Query 2: Using string_agg(), here problem with an order.
Step 1: Add days into temp table
create temp table temp_days
(
id int,
days varchar
);
insert into temp_days values(1,'Monday'),(2,'Tuesday'),(3,'Wednesday'),(4,'Thursday'),(5,'Friday'),(6,'Saturday'),(7,'Sunday');
Step 2: Join with main table
select d.day_ids,string_agg(distinct t.days,',')
from tbl_days d
inner join temp_days t on t.id = any(d.day_ids)
group by d.day_ids
step-by-step demo:db<>fiddle
SELECT
id,
string_agg( -- 4
CASE day -- 3
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
END,
','
ORDER BY index_in_array -- 4
)
FROM (
SELECT
*,
row_number() OVER () as id -- 1
FROM tbl_days
) s,
unnest(day_ids) WITH ORDINALITY as t(day, index_in_array) -- 2
GROUP BY id
For my approach you need an id column. This creates one. If you already have one, you can ignore this step
unnest() expands the array into one row per element. The WITH ORDINALITY clause adds an index to the records which saves the position of the element in the original array
Replace the numbers with the related string using a CASE clause
Reaggregate the weekdays by their original ids. The order can be ensured by using the in (2) created index, which can be using in the ORDER BY clause of the aggregate
I have two tables that need to be joined on the nearest date (nearest before date).
Screenshot of My Requirement
For example: In Table1 Date is 6/19/2018(M/DD/YYYY) then I would like to get the data of nearest before date from the Table2(If table has 07/19/2018, 06/20/2018 and 06/16/2018, I would like to get 06/16/2018 record information).
I have multiple records in table1 and want to get the nearest date record in form from the table2. Please see the image for more info about my requirement. Thank you in advance for your help.
Assuming that you must do it for every customer distinctly (customer column is a key in the example). If you have another key (let's say customer, item_name, item_name column is not shown, add it manually in this case), then change the corresponding predicates (to a[2].customer=x.customer and a[2].item_name=x.item_name in the example). If you don't want to do it for each customer, just remove the predicates a[2].customer=x.customer and.
You can run the statement below AS IS to check.
with
xyz (customer, req_del_date) as (values
('ABC', date('2018-06-19'))
, ('ABC', date('2018-09-04'))
, ('ABC', date('2018-04-24'))
, ('ABC', date('2018-03-17'))
)
, abc (customer, actual_del_date) as (values
('ABC', date('2018-11-20'))
, ('ABC', date('2018-06-12'))
, ('ABC', date('2018-05-09'))
, ('ABC', date('2018-04-27'))
, ('ABC', date('2018-04-14'))
, ('ABC', date('2017-12-31'))
, ('ABC', date('2017-12-30'))
)
select x.customer, x.req_del_date, a.actual_del_date, a.diff_days
from xyz x, table (
select a.customer, a.actual_del_date
, days(x.req_del_date) - days(actual_del_date) diff_days -- just for test
-- other columns from abc if needed
from abc a
where a.customer=x.customer and x.req_del_date>=a.actual_del_date
and (days(x.req_del_date) - days(a.actual_del_date)) =
(
select min(days(x.req_del_date) - days(a2.actual_del_date))
from abc a2
where a2.customer=x.customer and x.req_del_date>=a2.actual_del_date
)
) a;
I have a table of item price changes, and I want to use it to create a table of item prices for each date (between the item's launch and end dates).
Here's some code to create the date:-
declare #Item table (item_id int, item_launch_date date, item_end_date date);
insert into #Item Values (1,'2001-01-01','2016-01-01'), (2,'2001-01-01','2016-01-01')
declare #ItemPriceChanges table (item_id int, item_price money, my_date date);
INSERT INTO #ItemPriceChanges VALUES (1, 123.45, '2001-01-01'), (1, 345.34, '2001-01-03'), (2, 34.34, '2001-01-01'), (2,23.56 , '2005-01-01'), (2, 56.45, '2016-05-01'), (2, 45.45, '2017-05-01'); ;
What I'd like to see is something like this:-
item_id date price
------- ---- -----
1 2001-01-01 123.45
1 2001-01-02 123.45
1 2001-01-03 345.34
1 2001-01-04 345.34
etc.
2 2001-01-01 34.34
2 2001-01-02 34.34
etc.
Any suggestions on how to write the query?
I'm using SQL Server 2016.
Added:
I also have a calendar table called "dim_calendar" with one row per day. I had hoped to use a windowing function, but the nearest I can find is lead() and it doesn't do what I thought it would do:-
select
i.item_id,
c.day_date,
ipc.item_price as item_price_change,
lead(item_price,1,NULL) over (partition by i.item_id ORDER BY c.day_date) as item_price
from dim_calendar c
inner join #Item i
on c.day_date between i.item_launch_date and i.item_end_date
left join #ItemPriceChanges ipc
on i.item_id=ipc.item_id
and ipc.my_date=c.day_date
order by
i.item_id,
c.day_date;
Thanks
I wrote this prior to your edit. Note that your sample output suggests that an item can have two prices on the day of the price change. The following assumes that an item can only have one price on a price change day and that is the new price.
declare #Item table (item_id int, item_launch_date date, item_end_date date);
insert into #Item Values (1,'2001-01-01','2016-01-01'), (2,'2001-01-01','2016-01-01')
declare #ItemPriceChange table (item_id int, item_price money, my_date date);
INSERT INTO #ItemPriceChange VALUES (1, 123.45, '2001-01-01'), (1, 345.34, '2001-01-03'), (2, 34.34, '2001-01-01'), (2,23.56 , '2005-01-01'), (2, 56.45, '2016-05-01'), (2, 45.45, '2017-05-01');
SELECT * FROM #ItemPriceChange
-- We need a table variable holding all possible date points for the output
DECLARE #DatePointList table (DatePoint date);
DECLARE #StartDatePoint date = '01-Jan-2001';
DECLARE #MaxDatePoint date = GETDATE();
DECLARE #DatePoint date = #StartDatePoint;
WHILE #DatePoint <= #MaxDatePoint BEGIN
INSERT INTO #DatePointList (DatePoint)
SELECT #DatePoint;
SET #DatePoint = DATEADD(DAY,1,#DatePoint);
END;
-- We can use a CTE to sequence the price changes
WITH ItemPriceChange AS (
SELECT item_id, item_price, my_date, ROW_NUMBER () OVER (PARTITION BY Item_id ORDER BY my_date ASC) AS SeqNo
FROM #ItemPriceChange
)
-- With the price changes sequenced, we can derive from and to dates for each price and use a join to the table of date points to produce the output. Also, use an inner join back to #item to only return rows for dates that are within the start/end date of the item
SELECT ItemPriceDate.item_id, DatePointList.DatePoint, ItemPriceDate.item_price
FROM #DatePointList AS DatePointList
INNER JOIN (
SELECT ItemPriceChange.item_id, ItemPriceChange.item_price, ItemPriceChange.my_date AS from_date, ISNULL(ItemPriceChange_Next.my_date,#MaxDatePoint) AS to_date
FROM ItemPriceChange
LEFT OUTER JOIN ItemPriceChange AS ItemPriceChange_Next ON ItemPriceChange_Next.item_id = ItemPriceChange.item_id AND ItemPriceChange.SeqNo = ItemPriceChange_Next.SeqNo - 1
) AS ItemPriceDate ON DatePointList.DatePoint >= ItemPriceDate.from_date AND DatePointList.DatePoint < ItemPriceDate.to_date
INNER JOIN #item AS item ON item.item_id = ItemPriceDate.item_id AND DatePointList.DatePoint BETWEEN item.item_launch_date AND item.item_end_date
ORDER BY ItemPriceDate.item_id, DatePointList.DatePoint;
#AlphaStarOne Perfect! I've modified it to use a Windowing function rather than a self-join, but what you've suggested works. Here's my implementation of that in case anyone else needs it:
SELECT
ipd.item_id,
dc.day_date,
ipd.item_price
FROM dim_calendar dc
INNER JOIN (
SELECT
item_id,
item_price,
my_date AS from_date,
isnull(lead(my_date,1,NULL) over (partition by item_id ORDER BY my_date),getdate()) as to_date
FROM #ItemPriceChange ipc1
) AS ipd
ON dc.day_date >= ipd.from_date
AND dc.day_date < ipd.to_date
INNER JOIN #item AS i
ON i.item_id = ipd.item_id
AND dc.day_date BETWEEN i.item_launch_date AND i.item_end_date
ORDER BY
ipd.item_id,
dc.day_date;
I prepared the following report. It displays three columns (AccruedInterest, Tip & CardRevenue) by month for a given year. Now I want to "Rotate" the result so that StartDate value turn into 12 columns.
I have this:
I need this:
I have tried pivoting the table but I need multiple columns to be aggregated as you see.
You have to unpivot your data and the pivot.
Note: I put in my own values in your table as to make each unique so you know the data is correct.
--Create YourTable
SELECT * INTO YourTable
FROM
(
SELECT CAST('2015-01-01' AS DATE) StartDate,
607.834 AS AccruedInterest,
1 AS Tip,
3 AS CardRevenue
UNION ALL
SELECT CAST('2015-02-01' AS DATE) StartDate,
643.298 AS AccruedInterest,
16.8325 AS Tip,
5 AS CardRevenue
) A;
GO
--This pivots your data
SELECT *
FROM
(
--This unpivots your data using cross apply
SELECT col,val,StartDate
FROM YourTable
CROSS APPLY
(
SELECT 'AccruedInterest', CAST(AccruedInterest AS VARCHAR(100))
UNION ALL
SELECT 'Tip', CAST(Tip AS VARCHAR(100))
UNION ALL
SELECT 'CardRevenue', CAST(CardRevenue AS VARCHAR(100))
) A(col,val)
) B
PIVOT
(
MAX(val) FOR startdate IN([2015-01-01],[2015-02-01])
) pvt
Results:
col 2015-01-01 2015-02-01
AccruedInterest 607.834 643.298
CardRevenue 3 5
Tip 1.0000 16.8325