I am trying to do a union all to produce data for reporting, below is what I have so far, it shows all the data I want but I cannot get the data in the same rows, it produces the two rows at a minimum with null in the corresponding column. I am hoping that there is a way so that I can get the data in the same row?
select account, campaign, sale, date
from
(
SELECT CHACCOUNTNO as account, CONTSUPREF as campaign,null as sale, ONDATE as date
FROM dbo.MKTDW
WHERE (RESULTCODE = 'D01') and CONTACT IN ('Campaign ID')
group by CHACCOUNTNO, CONTSUPREF, ONDATE
UNION ALL
SELECT CHACCOUNTNO as account, null as campaign, CONTSUPREF as sale, ONDATE as date
FROM dbo.MKTDW
WHERE (RESULTCODE = 'D01') and CONTACT IN ('Order')
group by CHACCOUNTNO, CONTSUPREF, ONDATE
)account
group by account,campaign,sale,date
order by account
Current Result:
account campaign sale date
A2043056003(2IJUMI M NULL N177618 2014-07-21 00:00:00.000
A2043056003(2IJUMI M LT08704 NULL 2014-07-21 00:00:00.000
Expected result:
A2043056003(2IJUMI M) LT08704 N177618 2014-07-21 00:00:00.000
The answer will be straightforward. If you want something shown in horizontal way, use JOINs instead of SET operators. The code is listed below, tested, works perfect in SSMS. :)
--create table structure
create table dbo.MKTDW
( CHACCOUNTNO varchar(100),
CONTSUPREF varchar(10),
RESULTCODE varchar(10),
CONTACT varchar(50),
ONDATE datetime)
go
--insert sample data
insert dbo.MKTDW
select 'A2043056003(2IJUMI M)', 'N177618', 'D01', 'Order', '2014-07-21 00:00:00.000'
union all
select 'A2043056003(2IJUMI M)', 'LT08704', 'D01', 'Campaign ID', '2014-07-21 00:00:00.000'
union all
select 'B2043056003(2IJUMI M)', 'M000000', 'D01', 'Order', '2014-07-21 00:00:00.000'
union all
select 'B2043056003(2IJUMI M)', 'X111111', 'D01', 'Campaign ID', '2014-07-21 00:00:00.000'
--below is the solution
select a.CHACCOUNTNO as account,
a.CONTSUPREF as campaign,
b.CONTSUPREF as sale,
a.ondate as date
from dbo.MKTDW as a
join dbo.MKTDW as b
on a.CHACCOUNTNO = b.CHACCOUNTNO
where a.CONTACT = 'campaign id'
and b.CONTACT = 'order'
and a.RESULTCODE = 'D01'
and b.RESULTCODE = 'D01'
RESULT:
There is no need of union all and specifying null ..
SELECT CHACCOUNTNO as account, CONTSUPREF as campaign,CONTSUPREF as sale, ONDATE as date
FROM dbo.MKTDW
WHERE (RESULTCODE = 'D01') and CONTACT IN ('Campaign ID','order')
group by CHACCOUNTNO, CONTSUPREF, ONDATE
When any of the above group by columns have null,Null will be treated as seperate group
Related
I have a situation where we record every change to a column of a member record in the MEMBER table as a separate row. The changes that are logged only show the column name that was changed, the new value and the date the change was made.
Example: Table Name - CustomerChanges
customerId
columnName
newValue
dateChanged
1234
status
Active
1/12/2021
1234
status
Cancelled
9/30/2020
1234
status
Frozen
7/1/2020
1234
status
Active
1/1/2020
5678
status
Active
1/11/2021
5678
status
Frozen
11/1/2020
5678
status
Active
2/1/2020
9101
status
Active
1/10/2021
9101
type
Full Time
1/10/2021
9101
status
Frozen
10/15/2020
9101
status
Active
1/1/2020
I need to do 3 things here:
Pull all of the changes for status only
Grab all of the Customers that went from Frozen to Active. I believe the way to go about it is to pull the top two records for each customerId and then see who went from Frozen to Active.
Who did it in the specified time frame...in this example it would be January.
Considerations:
As stated earlier, the table has changes for all columns just not the status column.
The Customer can have many entries of status changes.
I am working in SSRS and have been struggling with this. I tried to query the table for changes to the column columnName, group it by CustomerId, grab the top two rows and then see which ones went from Frozen to Active.
Thank you in advance.
This is a bit messy, there is probably a better way of doing it but this should work.
You will have to adjust the inner-most query to filter on your date range, I didn't include that bit.
I recreated your dataset and then produced the output as follows
DECLARE #t TABLE (customerId int, columnName varchar(20), newValue varchar(20), dateChanged date)
INSERT INTO #t VALUES
(1234, 'status', 'Active' , '2021-01-12' ),
(1234, 'status', 'Cancelled', '2020-09-30' ),
(1234, 'status', 'Frozen' , '2020-07-01' ),
(1234, 'status', 'Active' , '2020-01-01' ),
(5678, 'status', 'Active' , '2021-01-11' ),
(5678, 'status', 'Frozen' , '2020-11-01' ),
(5678, 'status', 'Active' , '2020-02-01' ),
(9101, 'status', 'Active' , '2021-01-10' ),
(9101, 'type', 'Full Time' , '2021-01-10' ),
(9101, 'status', 'Frozen' , '2020-10-15' ),
(9101, 'status', 'Active' , '2020-01-01' )
--SELECT * FROM #t
SELECT * FROM
(
SELECT
s.*
, LAG(newValue, 1) OVER(PARTITION BY customerId ORDER BY dateChanged) AS PreviousStatus
FROM
(SELECT *, ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY dateChanged DESC) as RowN
FROM #t
WHERE columnName = 'Status' -- Add date filtering here
) s
WHERE s.RowN < =2
) x
WHERE newValue = 'Active' AND PreviousStatus = 'Frozen'
This gives the following output
I have a table that stores the VIN numbers and delivery dates of vehicles based on a code. I want to be able to get one row with three columns of data.
I have tried the following
SELECT DISTINCT VIN, MAX(TRANSACTION_DATE) AS DELIVERY_DATE
FROM "TABLE"
WHERE DELIVERY_TYPE ='025'
AND VIN IN ('XYZ')
GROUP BY VIN
UNION ALL
SELECT VIN, MAX(TRANSACTION_DATE) AS OTHER_DELIVERY_DATE
FROM "TABLE"
WHERE DELIVERY_TYPE !='025'
AND VIN IN ('XYZ')
GROUP BY VIN;
When I run this I get
VIN DELIVERY_DATE
XYZ 26-dec-18
XYZ 01-MAY-19
current data format in table:
VIN TRANSACTION_DATE
XYZ 26-DEC-18
XYZ 01-MAY-19
Required format:
VIN DELIVERY_DATE OTHER_DELIVERY DATE
XYZ 26-DEC-18 01-MAY-19
use conditional aggregation
SELECT VIN,
MAX (CASE WHEN DELIVERY_TYPE ='025' AND
VIN IN ('XYZ') then TRANSACTION_DATE end) AS DELIVERY_DATE
MAX(CASE WHEN DELIVERY_TYPE !='025' AND
VIN IN ('XYZ') then TRANSACTION_DATE end) AS OTHER_DELIVERY
FROM "TABLE"
GROUP BY VIN
Just use conditional aggregation:
SELECT VIN,
MAX(CASE WHEN DELIVERY_TYPE = 25 THEN TRANSACTION_DATE END) AS DELIVERY_DATE,
MAX(CASE WHEN DELIVERY_TYPE <> 25 THEN TRANSACTION_DATE END) AS TRANSACTION_DATE
FROM TABLE
WHERE VIN IN ('XYZ')
GROUP BY VIN;
Note that SELECT DISTINCT is almost never used with GROUP BY.
You can use CROSS APPLY
DECLARE #Cars TABLE (VIN VARCHAR(100), DELIVERY_TYPE VARCHAR(3), TRANSACTION_DATE DATE)
INSERT INTO #Cars
(VIN, DELIVERY_TYPE , TRANSACTION_DATE)
VALUES
('XYZ', '025', '20181226'), ('XYZ', '030', '20190319')
I needed above code to be able to run without a table and data, all you need is this:
SELECT DISTINCT C.VIN, DD.DELIVERY_DATE, TD.TRANSACTION_DATE
FROM #Cars C
CROSS APPLY (SELECT MAX(TRANSACTION_DATE) DELIVERY_DATE FROM #Cars D WHERE D.DELIVERY_TYPE = '025' AND D.VIN = C.VIN) DD
CROSS APPLY (SELECT MAX(TRANSACTION_DATE) TRANSACTION_DATE FROM #Cars D WHERE D.DELIVERY_TYPE = '025' AND D.VIN = C.VIN) TD
If you need to transpond not two but a lot more columns, I'd suggest using PIVOT TABLE as more appropriate, but for two columns either CROSS APPLY or conditional aggregation will do the trick.
I have a table with the following setup
ID InOut_Status InOut_Datetime
1 IN 9/12/2017 8:00
2 IN 9/12/2017 10:00
1 OUT 9/12/2017 1:00
2 OUT 9/12/2017 3:00
I want to be able to see both status and date on the same row vs separate rows for example
ID In_Status In_Datetime Out_Status Out_Datetime
1 IN 9/12/2017 8:00 OUT 9/12/2017 1:00
2 IN 9/12/2017 10:00 OUT 9/12/2017 3:00
I would like to return all columns. I just provided a few for example. I also would like to show only the most recent Datetime for each ID and if the user hasn't checked out, I would like for the Out_Datetime to be blank.
Any assistance would be greatly appreciated. Thanks.
You can use self join:
SELECT *
FROM
(
SELECT ins.id
, ins.InOut_Datetime as in_time
, outs.InOut_Datetime as out_time
, row_number() over (partition by ins.id order by ins.InOut_Datetime desc) as ranking
FROM table ins
LEFT JOIN table outs
ON ins.id = outs.id
AND outs.InOut_Status = 'OUT'
AND outs.InOut_Datetime > ins.InOut_Datetime
WHERE ins.InOut_Status = 'IN'
and ins.InOut_Datetime > DATEADD(day, -1, GETDATE())
) t
WHERE t.ranking = 1
Updated query to :
get logins within last 24 hours
get the latest login of a user only
show out time only if it's later than in time
You need to left join, however, you want to limit the join to the first record returned in descending order using a sub query.
SELECT * FROM
(
SELECT
ID,InOut_Status,InOutDateTime,
CheckOutInstanceDescending = ROW_NUMBER() OVER(PARTITION BY ClockOut.ID ORDER BY ClockOut.InOutDateTime DESC)
FROM
MyTable ClockIn
LEFT OUTER JOIN MyTable ClockOut ON ClockOut.ID=ClockIn.ID
WHERE
ClockIn.InOut_Status='IN'
)AS Combined
WHERE
Combined.CheckOutInstanceDescending=1
A simple pivot would fix this the easiest:
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx. You can run this in SSMS 2008 or higher(I wrote it in SQL 2016).
DECLARE #Temp TABLE (Id INT, InOut_Status VARCHAR(8), InOut_Datetime DATETIME)
INSERT INTO #Temp (Id, InOut_Status, InOut_Datetime) VALUES (1, 'IN', '9-12-2017 8:00'), (2, 'IN', '9-12-2017 10:00'),(1, 'OUT', '9-12-2017 13:00'),(2, 'OUT', '9-12-2017 16:00'),(3, 'IN', '9-12-2017 06:00')
SELECT
pvt.Id
, 'IN' AS In_Status
, pvt.[In]
, 'OUT' AS Out_Status
, pvt.OUT
From #Temp
PIVOT(MAX(InOut_Datetime) FOR InOut_Status IN ([In], [OUT])) AS pvt
We have a time management system where our employees or contractors (resources) enter the hours they have worked, and we derive a cost for it. I have a table with the historic costs:
CREATE TABLE ResourceTimeTypeCost (
ResourceCode VARCHAR(32),
TimeTypeCode VARCHAR(32),
EffectiveDate DATETIME,
CostRate DECIMAL(12,2)
)
So I have one date field which marks the effective date. If we have a record which is
('ResourceA', 'Normal', '2012-04-30', 40.00)
and I add a record which is
('ResourceA', 'Normal', '2012-05-04', 50.00)
So all hours entered between the 30th April and the 3rd of May will be at £40.00, all time after midnight on the 4th will be at £50.00. I understand this in principle but how do you write a query expressing this logic?
Assuming my time table looks like the below
CREATE TABLE TimeEntered (
ResourceCode VARCHAR(32),
TimeTypeCode VARCHAR(32),
ProjectCode VARCHAR(32),
ActivityCode VARCHAR(32),
TimeEnteredDate DATETIME,
HoursWorked DECIMAL(12,2)
)
If I insert the following records into the TimeEntered table
('ResourceA','Normal','Project1','Management1','2012-04-30',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-01',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-02',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-03',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-04',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-07',7.5)
('ResourceA','Normal','Project1','Management1','2012-05-08',7.5)
I'd like to get a query that returns the total cost by resource
So in the case above it would be 'ResourceA', (4 * 7.5 * 40) + (3 * 7.5 * 50) = 2325.00
Can anyone provide a sample SQL query? I know this example doesn't make use of TimeType (i.e. it's always 'Normal') but I'd like to see how this is dealt with as well
I can't change the structure of the database. Many thanks in advance
IF OBJECT_ID ('tempdb..#ResourceTimeTypeCost') IS NOT NULL DROP TABLE #ResourceTimeTypeCost
CREATE TABLE #ResourceTimeTypeCost ( ResourceCode VARCHAR(32), TimeTypeCode VARCHAR(32), EffectiveDate DATETIME, CostRate DECIMAL(12,2) )
INSERT INTO #ResourceTimeTypeCost
SELECT 'ResourceA' as resourcecode, 'Normal' as timetypecode, '2012-04-30' as effectivedate, 40.00 as costrate
UNION ALL
SELECT 'ResourceA', 'Normal', '2012-05-04', 50.00
IF OBJECT_ID ('tempdb..#TimeEntered') IS NOT NULL DROP TABLE #TimeEntered
CREATE TABLE #TimeEntered ( ResourceCode VARCHAR(32), TimeTypeCode VARCHAR(32), ProjectCode VARCHAR(32), ActivityCode VARCHAR(32), TimeEnteredDate DATETIME, HoursWorked DECIMAL(12,2) )
INSERT INTO #TimeEntered
SELECT 'ResourceA','Normal','Project1','Management1','2012-04-30',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-01',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-02',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-03',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-04',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-07',7.5
UNION ALL SELECT 'ResourceA','Normal','Project1','Management1','2012-05-08',7.5
;with ranges as
(
select
resourcecode
,TimeTypeCode
,EffectiveDate
,costrate
,row_number() OVER (PARTITION BY resourcecode,timetypecode ORDER BY effectivedate ASC) as row
from #ResourceTimeTypeCost
)
,ranges2 AS
(
SELECT
r1.resourcecode
,r1.TimeTypeCode
,r1.EffectiveDate
,r1.costrate
,r1.effectivedate as start_date
,ISNULL(DATEADD(ms,-3,r2.effectivedate),GETDATE()) as end_date
FROM ranges r1
LEFT OUTER JOIN ranges r2 on r2.row = r1.row + 1 --joins onto the next date row
AND r2.resourcecode = r1.resourcecode
AND r2.TimeTypeCode = r1.TimeTypeCode
)
SELECT
tee.resourcecode
,tee.timetypecode
,tee.projectcode
,tee.activitycode
,SUM(ranges2.costrate * tee.hoursworked) as total_cost
FROM #TimeEntered tee
INNER JOIN ranges2 ON tee.TimeEnteredDate >= ranges2.start_date
AND tee.TimeEnteredDate <= ranges2.end_date
AND tee.resourcecode = ranges2.resourcecode
AND tee.timetypecode = ranges2.TimeTypeCode
GROUP BY tee.resourcecode
,tee.timetypecode
,tee.projectcode
,tee.activitycode
What you have is a cost table that is, as some would say, a slowly changing dimension. First, it will help to have an effective and end date for the cost table. We can get this by doing a self join and group by:
with costs as
(select c.ResourceCode, c.EffectiveDate as effdate,
dateadd(day, -1, min(c1.EffectiveDate)) as endDate,
datediff(day, c.EffectiveDate, c1.EffectiveDate) - 1 as Span
from ResourceTimeTypeCost c left outer join
ResourceTimeTypeCost c1
group by c.ResourceCode, c.EffectiveDate
)
Although you say you cannot change the table structure, when you have a slowly changing dimension, having an effective and end date is good practice.
Now, you can use this infomation with TimeEntered as following:
select te.*, c.CostRate * te.HoursWorked as dayCost
from TimeEntered te join
Costs c
on te.ResouceCode = c.ResourceCode and
te.TimeEntered between c.EffDate and c.EndDate
To summarize by Resource for a given time range, the full query would look like:
with costs as
(select c.ResourceCode, c.EffectiveDate as effdate,
dateadd(day, -1, min(c1.EffectiveDate)) as endDate,
datediff(day, c.EffectiveDate, c1.EffectiveDate) - 1 as Span
from ResourceTimeTypeCost c left outer join
ResourceTimeTypeCost c1
group by c.ResourceCode, c.EffectiveDate
),
te as
(select te.*, c.CostRate * te.HoursWorked as dayCost
from TimeEntered te join
Costs c
on te.ResouceCode = c.ResourceCode and
te.TimeEntered between c.EffDate and c.EndDate
)
select te.ResourceCode, sum(dayCost)
from te
where te.TimeEntered >= <date1> and te.TimeEntered < <date2>
You might give this a try. CROSS APPLY will find first ResourceTimeTypeCost with older or equal date and same ResourceCode and TimeTypeCode as current record from TimeEntered.
SELECT te.ResourceCode,
te.TimeTypeCode,
te.ProjectCode,
te.ActivityCode,
te.TimeEnteredDate,
te.HoursWorked,
te.HoursWorked * rttc.CostRate Cost
FROM TimeEntered te
CROSS APPLY
(
-- First one only
SELECT top 1 CostRate
FROM ResourceTimeTypeCost
WHERE te.ResourceCode = ResourceTimeTypeCost.ResourceCode
AND te.TimeTypeCode = ResourceTimeTypeCost.TimeTypeCode
AND te.TimeEnteredDate >= ResourceTimeTypeCost.EffectiveDate
-- By most recent date
ORDER BY ResourceTimeTypeCost.EffectiveDate DESC
) rttc
Unfortunately I can no longer find article on msdn, hence the blog in link above.
Live test # Sql Fiddle.
I have a set of Meeting rooms and meetings in that having start date and end Date. A set of meeting rooms belong to a building.
The meeting details are kept in MeetingDetail table having a startDate and endDate.
Now I want to fire a report between two time period say reportStartDate and reportEndDate, which finds me the time slots in which all the meeting rooms are booked for a given building
Table structure
MEETING_ROOM - ID, ROOMNAME, BUILDING_NO
MEETING_DETAIL - ID, MEETING_ROOM_ID, START_DATE, END_DATE
The query has to be fired for reportStartDate and REportEndDate
Just to clarify further, the aim is to find all the time slots in which all the meeting rooms were booked in a given time period of reportStartDate and reportEndDate
For SQL Server 2005+ you could try the following (see note at the end for mysql)
WITH TIME_POINTS (POINT_P) AS
(SELECT DISTINCT START_DATE FROM MEETING_DETAIL
WHERE START_DATE > #reportStartDate AND START_DATE < #reportEndDate
UNION SELECT DISTINCT END_DATE FROM MEETING_DETAIL
WHERE END_DATE > #reportStartDate AND END_DATE < #reportEndDate
UNION SELECT #reportEndDate
UNION SELECT #reportStartDate),
WITH TIME_SLICE (START_T, END_T) AS
(SELECT A.POINT_P, MIN(B.POINT_P) FROM
TIMEPOINTS A
INNER JOIN TIMEPOINTS B ON A.POINT_P > B.POINT_P
GROUP BY A.POINT_P),
WITH SLICE_MEETINGS (START_T, END_T, MEETING_ROOM_ID, BUILDING_NO) AS
(SELECT START_T, END_T, MEETING_ROOM_ID, BUILDING_NO FROM
TIME_SLICE A
INNER JOIN MEETING_DETAIL B ON B.START_DATE <= A.START_T AND B.END_DATE >= B.END_T
INNER JOIN MEETING_ROOM C ON B.MEETING_ROOM_ID = C.ID),
WITH SLICE_COUNT (START_T, END_T, BUILDING_NO, ROOMS_C) AS
(SELECT START_T, END_T, BUILDING_NO, COUNT(MEETING_ROOM_ID) FROM
SLICE_MEETINGS
GROUP BY START_T, END_T, BUILDING_NO),
WITH ROOMS_BUILDING (BUILDING_NO, ROOMS_C) AS
(SELECT BUILDING_NO, COUNT(ID) FROM
MEETING_ROOM
GROUP BY BUILDING_NO)
SELECT B.BUILDING_NO, A.START_T, A.END_T
FROM SLICE_COUNT A.
INNER JOIN ROOMS_BUILDING B WHERE A.BUILDING_NO = B.BUILDING_NO AND B.ROOMS_C = A.ROOMS_C;
what it does is (each step corresponds to each CTE definition above)
Get all the time markers, i.e. end or start times
Get all time slices i.e. the smallest unit of time between which there is no other time marker (i.e. no meetings start in a time slice, it's either at the beginning or at the end of a time slice)
Get meetings for each time slice, so now you get something like
10.30 11.00 Room1 BuildingA
10.30 11.00 Room2 BuildingA
11.00 12.00 Room1 BuildingA
Get counts of rooms booked per building per time slice
Filter out timeslice-building combinations that match the number of rooms in each building
Edit
Since mysql doesn't support the WITH clause you'll have to construct views for each (of the 5) WITH clases above. everything else would remain the same.
After reading your comment, I think I understand the problem a bit better. As a first step I would generate a matrix of meeting rooms and time slots using cross join:
select *
from (
select distinct start_date
, end_date
from #meeting_detail
) ts
cross join
#meeting_room mr
Then, for each cell in the matrix, add meetings in that timeslot:
left join
#meeting_detail md
on mr.id = md.meeting_room_id
and ts.start_date < md.end_date
and md.start_date < ts.end_date
And then demand that there are no free rooms. For example, by saying that the left join must succeed for all rooms and time slots. A left join succeeds if any field is not null:
group by
mr.building_no
, ts.start_date
, ts.end_date
having max(case when md.meeting_room_id is null
then 1 else 0 end) = 0
Here's a complete working example. It's written for SQL Server, and the table variables (#meeting_detail) won't work in MySQL. But the report generating query should work in most databases:
set nocount on
declare #meeting_room table (id int, roomname varchar(50),
building_no int)
declare #meeting_detail table (meeting_room_id int,
start_date datetime, end_date datetime)
insert #meeting_room (id, roomname, building_no)
select 1, 'Kitchen', 6
union all select 2, 'Ballroom', 6
union all select 3, 'Conservatory', 7
union all select 4, 'Dining Room', 7
insert #meeting_detail (meeting_room_id, start_date, end_date)
select 1, '2010-08-01 9:00', '2010-08-01 10:00'
union all select 1, '2010-08-01 10:00', '2010-08-01 11:00'
union all select 2, '2010-08-01 10:00', '2010-08-01 11:00'
union all select 3, '2010-08-01 10:00', '2010-08-01 11:00'
select mr.building_no
, ts.start_date
, ts.end_date
from (
select distinct start_date
, end_date
from #meeting_detail
) ts
cross join
#meeting_room mr
left join
#meeting_detail md
on mr.id = md.meeting_room_id
and ts.start_date < md.end_date
and md.start_date < ts.end_date
group by
mr.building_no
, ts.start_date
, ts.end_date
having max(case when md.meeting_room_id is null
then 1 else 0 end) = 0
This prints:
building_no start end
6 2010-08-01 10:00:00.000 2010-08-01 11:00:00.000