T-SQL get the last date time record - sql

My table looks like this:
+---------+------------------------+-------+---------+---------+
|channel |date |code |comment |order_id |
+---------+------------------------+-------+---------+---------+
|1 |2017-10-27 12:04:45.397 |2 |comm1 |1 |
|1 |2017-10-27 12:14:20.997 |1 |comm2 |1 |
|2 |2017-10-27 12:20:59.407 |3 |comm3 |1 |
|2 |2017-10-27 13:14:20.997 |1 |comm4 |1 |
|3 |2017-10-27 12:20:59.407 |2 |comm5 |1 |
|3 |2017-10-27 14:20:59.407 |1 |comm6 |1 |
+---------+------------------------+-------+---------+---------+
And I expect result like this:
+---------+------------------------+-------+---------+
|channel |date |code |comment |
+---------+------------------------+-------+---------+
|1 |2017-10-27 12:14:20.997 |1 |comm2 |
|2 |2017-10-27 13:14:20.997 |1 |comm4 |
|3 |2017-10-27 14:20:59.407 |1 |comm6 |
+---------+------------------------+-------+---------+
Always 1 record with order_id = x and max date for each channel. Total number of channels is constant.
My query works but I'm worried about performance as the table grows. Doing three almost identical queries doesn't seem smart.
select
*
from
(select top(1)
channel,
date,
code,
comment
from
status
where
channel = 1 and
order_id = 1 and
cast(date as date) = '2017-10-27'
order by
date desc) channel1
union
select
*
from
(select top(1)
channel,
date,
code,
comment
from
status
where
channel = 2 and
order_id = 1 and
cast(date as date) = '2017-10-27'
order by
date desc) channel2
union
select
*
from
(select top(1)
channel,
date,
code,
comment
from
status
where
channel = 3 and
order_id = 1 and
cast(date as date) = '2017-10-27'
order by
date desc) channel3
How can I improve this?

Another option is using the WITH TIES clause. No sub-query or extra field.
Select top 1 with ties *
From YourTable
Order By Row_Number() over (Partition By channel order by date desc)

Try using the ROW_NUMBER() function and a derived table. It will save you a lot of headaches. Try:
select channel
,date
,code
,comment
from
(select *
,row_number() over(partition by channel order by code asc) rn --probably don't need asc since it is ascending by default
from mytable) t
where t.rn = 1

Assuming you want the latest row for each channel, this would work.
SELECT *
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY s.channel ORDER BY [date] DESC) AS rn,
*
FROM [status] AS s
) AS t
WHERE t.rn = 1

Related

Nested Case when SQL Teradata

please I have the below data that I need to calculate the date diff between the Current_date and the Max Date for each ID for Active Status only and the result of the date diff will be located once beside the max date and the other records return NULL.
|ID |Date |Status |
|----+------|---------|
|A |1-Apr |Active |
|A |15-Apr|Active |
|B |1-Mar |Suspended|
|B |15-Mar|Deactive |
|C |1-Jan |Active |
|C |15-Jan|Active |
I tried to use the below query but it duplicates the result with each date
SELECT
ID,
Date,
CASE WHEN STATUS = 'Active' THEN
CASE WHEN Date = MAX(Date) OVER (PARTITION BY ID)
THEN CURRENT_DATE - MAX(Date) OVER (PARTITION BY ID) ELSE NULL END
ELSE NULL END AS Duration
FROM cte
ORDER BY ID, Date;
But I need the result to be like the below
|ID |Date |Status |Duration|
|----+------|---------|--------|
|A |1-Apr |Active |NULL |
|A |15-Apr|Active |19 |
|B |1-Mar |Suspended|NULL |
|B |15-Mar|Deactive |NULL |
|C |1-Jan |Active |NULL |
|C |15-Jan|Active |109 |
If you only want this on the most recent row with "ACTIVE", then add in row_number():
SELECT ID, Date,
(CASE WHEN STATUS = 'Active' AND
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DATE DESC) = 1
THEN CURRENT_DATE - Date
END) as Duration
FROM cte
ORDER BY ID, Date;
Note that your code looks like it should work, unless there are duplicate most recent dates for an id. However, this version is a bit simpler, eschewing the nested CASE expression and the unnecessary second call to MAX().
SELECT ID,
DATE,
Status,
CASE
WHEN STATUS = 'Active' AND RNUM = 1 THEN CURRENT_DATE - DATE
ELSE NULL
END AS Duration
FROM (SELECT ID,
DATE,
Status,
ROW_NUMBER() OVER (PARTITION BY ID,Status ORDER BY DATE DESC) AS RNUM
FROM CTE) CTE2
ORDER BY ID,
DATE;

how many rows have different value

Given a table events
sensor_id | event_type | value | time
----------+------------+--------+------------
2 |2 | 3.45 | 2014-02 (...)
2 |4 | (...) | (...)
2 |2 | (...) | (...)
3 |2 | (...) | (...)
2 |3 | (...) | (...)
Write an SQL query that returns a set of all sensors_id with the number of different event_types registered by each of them, ORDER BY sensor_id ASC
Query should return the following rowset
sensor_id | type
----------+------------
2 |3
3 |1
The names of the columns in the rowest don't matter, but their order does
My query:
SELECT
sensor_id, COUNT(*) AS `types`
FROM
`events`
GROUP BY
sensor_id
ORDER BY
sensor_id ASC
And result:
sensor_id | types
----------+------------
2 |4 <= error
3 |1
use distinct event_Type inside count
SELECT
sensor_id, COUNT(distinct event_type) AS `types`
FROM
`events`
GROUP BY
sensor_id
ORDER BY
sensor_id ASC
You can use window function:
select distinct sensor_id, types from (
SELECT
sensor_id, COUNT(distinct event_type) over(partition by sensor_id) AS `types`
FROM
`events` ) X
ORDER BY
sensor_id ASC;
Try this:
SELECT sensor_id, COUNT(DISTINCT event_type) as type
FROM #tbltemp
GROUP BY sensor_id
ORDER BY sensor_id
If you do not include count distinct value it will count no 2 two times (2,2,3,4).
If you put distinct it will count as (2,3,4) only.

Select two entry from a joined table (1:m)

I have a device table (dev) and device_date table (dev_data). Relationship 1:M
dev table:
| id |name |status |
|-----|-------|-------|
| 1 |a |111 |
|-----|-------|-------|
| 2 |b |123 |
|-----|-------|-------|
| ....|..... |.... |
dev_data table:
|id |dev_id |status |date |
|---|-------|--------|------------------------|
|1 |1 | 123 |2019-04-16T18:53:07.908Z|
|---|-------|--------|------------------------|
|2 |1 | 120 |2019-04-16T18:54:07.908Z|
|---|-------|--------|------------------------|
|3 |1 | 1207 |2019-04-16T18:55:07.908Z|
|---|-------|--------|------------------------|
|4 |2 | 123 |2019-04-16T18:53:08.908Z|
|---|-------|--------|------------------------|
|5 |2 | 121 |2019-04-16T18:54:08.908Z|
|---|-------|--------|------------------------|
|6 |2 | 127 |2019-04-16T18:55:08.908Z|
|...|.......|........|........................|
I need to select all dev and join dev_data, but add only 2 last records (by date)
the final response should look like this one:
status_calc_1 and status_calc_2 is diff between status in dev and dev_data
status_calc_1 => status difference of the last row from dev_data and dev
status_calc_2 => status difference of prelast row from dev_data and dev
|id |name |status_calc_1 | status_calc_2 |
|----|------|---------------|---------------|
|1 |a |1207-111 |120-111 |
|----|------|---------------|---------------|
|2 |b |127-123 |121-123 |
I tried this one:
select id, "name", status, max(dd.date) as last,
(select date from device_data p where p.dev_id = device.id and date < dd.date limit 1) as prelast
from device
inner join device_data dd on device.id = dd.dev_id
group by id, "name", status;
but get an error:
ERROR: subquery uses ungrouped column "device.id" from outer query
and this one:
select id, "name", status, max(dd.date) as last, max(dd2.date) as prelast,
from device
inner join device_data dd on device.id = dd.dev_id
inner join device_data dd2 on device.id = dd2.dev_id and dd2.date < dd.date
group by id, "name", status;
I get correct 2 last dev_data, but still, have no idea how to make 2 columns status_calc_1 and status_calc_2
status_calc_1 = last row dev_data.status - dev.status
status_calc_2 = prelast row dev_data.status - dev.status
You can use conditional aggregation:
select d.id, d.name, d.status,
max(dd.date) as last,
max(case when dd.seqnum = 2 then dd.date end) as prelast,
(max(case when dd.seqnum = 1 then dd.status end) - d.status) as status_calc_1,
(max(case when dd.seqnum = 2 then dd.status end) - d.status) as status_calc_2
from device d join
(select dd.*,
row_number() over (partition by dd.dev_id order by dd.date desc) as seqnum
from device_data dd
) dd
on d.id = dd.dev_id
where seqnum <= 2
group by d.id, d.name, d.status;

Produce a per customer per month sales report via SQL eg group by two columns and sum the third

I rarely write SQL (Azure SQL) however I am trying to generate a per month sales total per customer.
Customer:
|Username |ID |
|user1 |1 |
|user2 |2 |
|user3 |3 |
Order:
|CustomerId |Month |Total |
|1 |1 |275 |
|1 |1 |10 |
|2 |1 |100 |
|1 |3 |150 |
|2 |2 |150 |
|2 |2 |65 |
|3 |2 |150 |
I want to produce
|Username |Month1Total |Month2Total | Month3Total |
|user1 |285 |275 | 150 |
|user2 |100 |215 | 0 |
|user3 |0 |150 | 0 |
I can do the following
SELECT customerTable.Username Username, SUM(orderTable.OrderTotal) TotalMay
FROM "Order" orderTable
JOIN Customer customerTable ON orderTable.CustomerId = customerTable.Id
WHERE DATENAME(Month, (orderTable.PaidDateUTC)) = 'May'
GROUP BY Username
Which will give me an output per month. However I don't know how to loop this, do it per month and then group by username.
IF you want to have a separate column for each month then try this
SELECT customerTable.Username Username
,SUM(iif(ordertable.[month] = 1,orderTable.OrderTotal,0)) TotalJan
,SUM(iif(ordertable.[month] = 2,orderTable.OrderTotal,0)) TotalFeb
,SUM(iif(ordertable.[month] = 3,orderTable.OrderTotal,0)) TotalMar
,SUM(iif(ordertable.[month] = 4,orderTable.OrderTotal,0)) TotalApr
,SUM(iif(ordertable.[month] = 5,orderTable.OrderTotal,0)) TotalMay
FROM "Order" orderTable
JOIN Customer customerTable ON orderTable.CustomerId = customerTable.Id
GROUP BY Username
should be easy to add the remaining months
you can use case when
with t1 as
( select o.CustomerId,o.Month, sum(Total) as total from
[Order]
group by o.CustomerId,o.Month
) select c.Username,
case when t1.month=1 then t1.total else 0 end month1,
case when t1.month=2 then t1.total else 0 end month2,
case when t1.month=3 then t1.total else 0 end month3
from t1 join Customer c on t1.CustomerId=c.ID
Or you can use PIVOT
select c.username, t.* from
(
select * from
(select * from ord
) src
pivot
( sum(Total) FOR Month IN ([1],[2],[3])
) pvt
) as t join Customer c on t.CustomerId=c.ID
Something like this:
SELECT DATENAME(Month, (orderTable.PaidDateUTC)) MonthName,
customerTable.Username Username,
SUM(orderTable.OrderTotal) TotalMay
FROM "Order" orderTable
JOIN Customer customerTable ON orderTable.CustomerId = customerTable.Id
GROUP BY DATENAME(Month, (orderTable.PaidDateUTC)), Username
You just need to move the month name from the WHERE clause to the GROUP BY.
I would simply do JOIN with conditional aggregation :
SELECT c.Username,
SUM(CASE WHEN o.Month = 1 THEN o.Total ELSE 0 END) AS [Month1Total],
SUM(CASE WHEN o.Month = 2 THEN o.Total ELSE 0 END) AS [Month2Total],
SUM(CASE WHEN o.Month = 3 THEN o.Total ELSE 0 END) AS [Month3Total],
. . .
FROM Customer C INNER JOIN
Order o
ON o.CustomerId = c.id
GROUP BY c.Username;

How to query multiple COUNT(*) with good performance

I have this table:
CREATE TABLE schedule (
schedule_id serial NOT NULL,
start_date date,
CONSTRAINT schedule_id PRIMARY KEY (schedule_element_id)
)
And this table:
CREATE TABLE schedule_user (
schedule_user_id serial NOT NULL,
schedule_id integer,
state int,
CONSTRAINT fk_schedule_id FOREIGN KEY (schedule_id)
REFERENCES schedule (schedule_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
schedule
-------------------------
|schedule_id |date |
|------------+------------|
|1 |'2013-10-10'|
|2 |'2013-10-20'|
|3 |'2013-08-13'|
-------------------------
schedule_user
-----------------------------------
|schedule_user_id|schedule_id |state|
|----------------+------------+-----|
|1 | 1 |0 |
|2 | 1 |1 |
|3 | 1 |2 |
|4 | 1 |0 |
|5 | 1 |1 |
|6 | 1 |1 |
|4 | 2 |0 |
|5 | 2 |1 |
|7 | 2 |0 |
|2 | 3 |1 |
-----------------------------------
And I want a table like this:
characteristic
---------------------------------------
|schedule_id |state0|state1|state2|total|
|------------+------+------+------+-----|
|1 |2 |3 |1 |6 |
|2 |2 |1 |0 |3 |
|3 |1 |1 |0 |2 |
---------------------------------------
I've made this query that looks as as horrible as it's performance.
SELECT
schedule.schedule_id AS id,
(( SELECT count(*) AS count
FROM schedule_user
WHERE schedule_user.schedule_id = schedule.schedule_id
AND state=0))::integer AS state0,
(( SELECT count(*) AS count
FROM schedule_user
WHERE schedule_user.schedule_id = schedule.schedule_id
AND state=1))::integer AS state1,
(( SELECT count(*) AS count
FROM schedule_user
WHERE schedule_user.schedule_id = schedule.schedule_id
AND state=2))::integer AS state2,
(( SELECT count(*) AS count
FROM schedule_user
WHERE schedule_user.schedule_id = schedule.schedule_id))::integer
AS total
FROM schedule
Is there a better way to perform such a query?
Should I create an Index to 'state' column? if so, how should it look like?
You want to make a pivot table. An easy way to make one in SQL if you know all of the possible values of state beforehand is using sum and case statements.
select schedule_id,
sum(case state when 0 then 1 else 0 end) as state0,
sum(case state when 1 then 1 else 0 end) as state1,
sum(case state when 2 then 1 else 0 end) as state2,
count(*) as total
from schedule_user
group by schedule_id;
Another way is to use the crosstab table function.
Neither of these will let you get away with not knowing the set of values of state (and hence the columns in the result set).
I would try
SELECT s.schedule_id,
COUNT(CASE WHEN su.state = 0 THEN 1 END) AS state0,
COUNT(CASE WHEN su.state = 1 THEN 1 END) AS state1,
COUNT(CASE WHEN su.state = 2 THEN 1 END) AS state2,
COUNT(su.state) AS total
FROM schedule s
LEFT
OUTER
JOIN schedule_user su
ON su.schedule_id = s.schedule_id
GROUP
BY s.schedule_id
;
Ths standard approach is to use SUM() with a CASE over a JOIN with a GROUP BY:
SELECT
schedule.schedule_id AS id,
SUM (case when state=0 then 1 else 0 end) AS state0,
SUM (case when state=1 then 1 else 0 end) AS state1,
SUM (case when state=2 then 1 else 0 end) AS state2,
count(*) AS total
FROM schedule
LEFT JOIN schedule_user
ON schedule_user.schedule_id = schedule.schedule_id
GROUP BY 1