Alternative to STRING_AGG in with SQL - sql

I have a table as below
| activityName | UserID | deviceID | createdDate |
|------------------------------------------------------------|
| ON | 1 | adddsad |2020-01-09 00:02:59.477 |
| OFF | 1 | adddsad |2020-01-09 00:50:39.857 |
| ON | 2 | bdddsad |2020-01-09 00:51:11.480 |
| OFF | 2 | bdddsad |2020-01-09 00:51:19.450 |
when I use STRING_AGG like this which is accurate and returns the desired result
SELECT STRING_AGG(activityName + ' - ' + CONVERT(varchar, createdDate), ' | ') AS tag,
deviceID,
UserID
FROM (SELECT tag,
deviceID,
UserID
FROM tbl_DailyLogMaster
WHERE CONVERT(date, createdDate) = CONVERT(date, GETDATE())
GROUP BY userID) a
GROUP BY UserID;
It will return like this
| tag | deviceID | UserID |
|------------------------------------------------------------------------------------|
| ON - 2020-01-09 00:02:59.477 | OFF - 2020-01-09 00:50:39.857 | adddsad | 1 |
| ON - 2020-01-09 00:51:11.480 | OFF - 2020-01-09 00:51:19.450 | bdddsad | 2 |
On production I have SQL Server 2014 running and had to work on alternative for STRING_AGG which is not supported on older version
here is alternative I created
SELECT deviceID,
UserID,
STUFF((SELECT activityName + ' - ' + CONVERT(varchar, createdDate)
FROM tbl_DailyLogMaster
WHERE userID = tbl_DailyLogMaster.UserID
AND CONVERT(date, createdDate) = CONVERT(date, GETDATE())
ORDER BY UserID
FOR XML PATH('')),1,1,'') AS tag
FROM tbl_DailyLogMaster
WHERE CONVERT(date, createdDate) = CONVERT(date, GETDATE())
GROUP BY UserID,
deviceID,
UserID,
createdDate,
activityName;
it returns like this
| tag | deviceID | UserID |
|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| N - Jan 9 2020 12:51AMOFF - Jan 9 2020 12:51AMON - Jan 9 2020 12:02AMOFF - Jan 9 2020 12:50AM | OFF - 2020-01-09 00:50:39.857 | adddsad | 1 |
| N - Jan 9 2020 12:51AMOFF - Jan 9 2020 12:51AMON - Jan 9 2020 12:02AMOFF - Jan 9 2020 12:50AM | OFF - 2020-01-09 00:50:39.857 | adddsad | 1 |
| N - Jan 9 2020 12:51AMOFF - Jan 9 2020 12:51AMON - Jan 9 2020 12:02AMOFF - Jan 9 2020 12:50AM | OFF - 2020-01-09 00:50:39.857 | bdddsad | 2 |
| N - Jan 9 2020 12:51AMOFF - Jan 9 2020 12:51AMON - Jan 9 2020 12:02AMOFF - Jan 9 2020 12:50AM | OFF - 2020-01-09 00:50:39.857 | bdddsad | 2 |
What I am doing wrong with second query?

A some what blind guess, but I think this is the correct answer. you needed to ensure the subquery was properly correlated:
SELECT deviceID,
UserID,
STUFF((SELECT ' | ' + sq.activityName + ' - ' + CONVERT(varchar(20),sq.createdDate, 0)
FROM tbl_DailyLogMaster sq
WHERE DLM.UserID = sq.UserId
AND DLM.DeviceID = sq.DeviceID
AND sq.createdDate >= CONVERT(date, GETDATE())
AND sq.createdDate < DATEADD(DAY, 1, CONVERT(date, GETDATE()))
ORDER BY CreatedDate
FOR XML PATH(''),TYPE).value('.','varchar(MAX)'),1,3,'') AS tag --As yuou have no leading separator, no need for STUFF
FROM tbl_DailyLogMaster DLM
WHERE DLM.createdDate >= CONVERT(date, GETDATE())
AND DLM.createdDate < DATEADD(DAY, 1, CONVERT(date, GETDATE()))
GROUP BY UserID,
DeviceID;

Related

How to insert a Calender Items into Oracle DB records? [duplicate]

This question already has answers here:
How to populate calendar table in Oracle?
(3 answers)
Calendar table in SQL
(3 answers)
Closed 3 years ago.
I'm trying to insert a full year Calender into an ORACLE DB records
MY Columns are
----------------------------------------------------------------
| [FULL_DATE] | [DAY] | [MONTH_NAME] | [MONTH_NUMBER] | [YEAR] |
----------------------------------------------------------------
Function
(
#DATEFROM AS DATE
#DATETO AS DATE
) RETURNS DATE
AS
BEGIN
set #datefrom = '01/01/1995'
set #dateto = '31/12/1996'
while(#datefrom < #dateto)
BEGIN set #datefrom = DATEADD(day , 1 , #datefrom)
insert into SHEMA.DIM_TIME_TABLE ( FULL_DATE , DAY , MONTH , YEAR ) select DAY(GETDATE(#datefrom)) , DATENAME(MONTH , #datefrom), MONTH(GETDATE(#datefrom)) , YEAR(GETDATE(#datefrom))
END
RETURN
END
EXPECTED :
---------------------------------------------------------------
01 / 01 /1995 | 01 | JAN | 01 | 1995
---------------------------------------------------------------
02 / 01 /1995 | 02 | JAN | 01 | 1995
---------------------------------------------------------------
03 / 01 /1996 | 03 | JAN | 01 | 1995
In Oracle, you can use a recursive query to generate the date series, and then generate the expected columns in the outer query:
create table dim_time_table as
select
dt full_date,
extract(day from dt) day,
to_char(dt, 'month') month_name,
extract(month from dt) month_number,
extract(year from dt) year
from (
select to_date('1995-01-01', 'yyyy-mm-dd') + level - 1 as dt
from dual
connect by
to_date('1995-01-01', 'yyyy-mm-dd') + level
<= to_date('1997-01-01', 'yyyy-mm-dd')
)
Demo on DB Fiddle:
FULL_DATE | DAY | MONTH_NAME | MONTH_NUMBER | YEAR
:-------- | --: | :--------- | -----------: | ---:
01-JAN-95 | 1 | january | 1 | 1995
02-JAN-95 | 2 | january | 1 | 1995
03-JAN-95 | 3 | january | 1 | 1995
04-JAN-95 | 4 | january | 1 | 1995
05-JAN-95 | 5 | january | 1 | 1995
06-JAN-95 | 6 | january | 1 | 1995
07-JAN-95 | 7 | january | 1 | 1995
...

Dynamically month and year pivot with bad sorting

From the answer from here I build a solution that is good for me but I have still one problem.
I had table:
ID | Year | Month | Multiply | Future | Current
123 | 2017 | 1 | 1.0 | 25 | null
123 | 2017 | 2 | 1.0 | 19 | 15
123 | 2017 | 3 | 1.0 | 13 | 0
123 | 2017 | 4 | 1.0 | 22 | 14
123 | 2017 | 5 | 1.0 | 13 | null
... | .... | ... | ... | .. | ..
123 | 2018 | 1 | 1.0 | 25 | 10
123 | 2018 | 2 | 1.0 | 25 | 10
... | .... | ... | ... | .. | ..
124 | 2017 | 1 | 1 | 10 | 5
124 | 2017 | 2 | 1 | 15 | 2
... | .... | ... | ... | .. | ..
124 | 2018 | 1 | 1 | 20 | 0
I build this view to concatenate Year + Month and make IF statement:
value in the new Value column I'm getting from Future and Current column - when the Current value is null get the Future value and multiply by Multiply, else get Current value and multiply by Multiply (even 0). Next to it I need to add a 'F' prefix when the value is got from Future column.
ID | Date | Value |
123 | 2017 - 1 | F25 |
123 | 2017 - 2 | 15 |
123 | 2017 - 3 | 0 |
.. | .. | .. |
Code for it:
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM ( SELECT ID,
cast([Year] as varchar(30)) + ' - ' + cast([Month]as varchar(30)) as [Date],
[Multiply],
case when [Current] IS NULL /*OR [Current] = 0*/
then 'F' + CAST([Future] * [Multiply] as varchar(30))
else CAST([Current] * [Multiply] as varchar(30))
end as Value
FROM dbo.CurrentFuture
) AS t
And from this I make this view via dynamically pivot.
ID | 2017 - 1 | 2017 - 10 | 2017 - 11 | 2017 - 12 | 2017 - 2 | ... | 2018 - 1 | ...
123 | F25 | .. | .. | .. | 15 | ... | 10 | ...
124 | 5 | 2 | .. | .. | .. | ... | 0 | ...
Code for it:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date]
order by [Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [ID],' + #cols + ' from
(
select [ID], [Date],[Value]
from [dbo].[UpperView]
) x
pivot
(
max([Value])
for [Date] in (' + #cols + ')
) p '
execute(#query);
As you can see columns in the new view are not sorting in a good way.. instead of 2017 - 1, 2017 - 2, 2017 - 3 I have 2017 - 1, 2017 - 10, 2017 - 11, 2017 - 12, 2017 - 2. Can you help me how to sort it properly?
From the limited information, What you want is the ordering of the column based on the Concatenated string of Year+ Month.
What you need is to prefix the month with "0" for January - September and no prefix for October-December.
so in effect you will achieve this.
ID | 2017 - 01 | 2017 - 02 | 2017 - 03 | ..... | 2017 - 09 |2018 - 10 |2018 - 11||2018 - 12|
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM
(
SELECT ID,
CAST([Year] AS VARCHAR(30))+' - '+RIGHT('0'+CAST([Month] AS VARCHAR(30)), 2) AS [Date],
[Multiply],
CASE
WHEN [Current] IS NULL
/*OR [Current] = 0*/
THEN 'F'+CAST([Future] * [Multiply] AS VARCHAR(30))
ELSE CAST([Current] * [Multiply] AS VARCHAR(30))
END AS Value
FROM dbo.CurrentFuture
) AS t;
Add new column to UpperView for sorting like this
cast([Year] as varchar(30)) + RIGHT('0' + cast([Month] as varchar(30)), 2) as [DateOrder]
and use this column for sorting at your column query instead of [Date]
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date], [DateOrder]
order by [DateOrder]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

All years and months between 2 dates SQL

I have a little question I have a table called project that looks like this:
---------------------------------------
ProjectId | StartDate | EndDate |
---------------------------------------
1 | 01/01/2015 | 31/12/2017|
Is it posible to get all months and years between those dates like this:
--------------------
| Month | Year |
--------------------
1 | 2015 |
2 | 2015 |
3 | 2015 |
4 | 2015 |
5 | 2015 |
6 | 2015 |
7 | 2015 |
8 | 2015 |
9 | 2015 |
10 | 2015 |
11 | 2015 |
12 | 2015 |
1 | 2016 |
2 | 2016 |
3 | 2016 |
4 | 2016 |
. | . |
. | . |
. | . |
12 | 2017 |
Here's a method using PostgreSQL functions generate_series and extract:
SELECT extract(month FROM date) AS month, extract(year FROM date) AS year
FROM (
SELECT generate_series('2015-01-01'::date, '2017-12-31'::date, '1 month'::interval) AS date
) AS date_range
https://www.postgresql.org/docs/current/static/functions-srf.html
https://www.postgresql.org/docs/current/static/functions-datetime.html
You'd need to modify this to use the dates from your table:
SELECT extract(month FROM range) AS month, extract(year FROM range) AS year
FROM (
SELECT generate_series(StartDate, EndDate, '1 month'::interval) AS range
FROM project
WHERE ProjectId = 1
) AS date_range
If your database is sql server, you can run the following code to get the result.
DECLARE #DateStart DATETIME = '2015-01-01'
DECLARE #DateEnd DATETIME = ' 2017-12-31';
WITH Dates AS
(
SELECT DATEADD(DAY, -(DAY(#DateStart) - 1), #DateStart) AS [Date]
UNION ALL
SELECT DATEADD(MONTH, 1, [Date])
FROM Dates
WHERE [Date] < DATEADD(DAY, -(DAY(#DateEnd) - 1), #DateEnd)
)
SELECT
MONTH([Date]) AS [Month],
YEAR([Date]) AS [Year]
FROM Dates;
Hope it will help. If you need more help, you can look at the following link
https://blog.sqlauthority.com/2014/12/22/sql-server-list-the-name-of-the-months-between-date-ranges-part-2/
In Sql server, query for your date type
;with datecte (date)
AS
(
SELECT Convert(date,'01/01/2015',105)
UNION ALL
SELECT DATEADD(month,1,date)
from datecte
where DATEADD(month,1,date)<= (Select Convert(date,'31/12/2017',105))
)
select month(date),YEAR(date) from datecte

SQL Query to make dynamic columns

Hi I have a query that gets the total count of incidents per month and year
I wanted a result that shows all distinct years in columns
Could you please advise how to make this query dynamic?
Expected Result:
Month 2013 2014 2015
January 8 0 12
February 9 6 10
March 12 1 9
April 10 13 27
May 9 22 15
June 27 4 20
July 15 12 22
August 20 2 2
September 22 5 10
October 10 8 12
November 0 7 0
December 0 15 0
Query
select DATENAME(MONTH,DateOpened) as Month,
sum(case when year(DateOpened) = '2015' then 1 else 0 end) as [2015],
sum(case when year(DateOpened) = '2014' then 1 else 0 end) as [2014]
from Incidents
group by DATENAME(MONTH,DateOpened), MONTH(DateOpened)
order by MONTH(DateOpened)
Thanks for your help!
You can use the PIVOT table operator instead, something like this:
SELECT *
FROM
(
SELECT
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS t
PIVOT
(
COUNT(DateOpened)
FOR Year IN([2013], [2014], [2015])
) AS p;
SQL Fiddle Demo
If you don't need to write the list of years and do it dynamically for any year, you have to use dynamic SQL to run the query dynamically, like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(DATENAME(Year,DateOpened))
from Incidents AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT * , '+ #cols + '
FROM
(
select
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS t
PIVOT
(
COUNT(DateOpened)
FOR Year IN(' + #cols + ')' +
') p';
execute(#query);
SQL Fiddle Demo
This will give you something like this:
| Month | 2014 | 2015 | 2014 | 2015 |
|-----------|------|------|------|------|
| April | 0 | 3 | 0 | 3 |
| August | 1 | 1 | 1 | 1 |
| December | 1 | 0 | 1 | 0 |
| February | 0 | 1 | 0 | 1 |
| July | 1 | 1 | 1 | 1 |
| September | 1 | 0 | 1 | 0 |
Note that: Both the queries won't list any month that has no dates on the original table. If you want to list any month that is not listed on the table with counts 0, you have to modify the anchor query so that it lists all the months even if not listed:
SELECT
m.Name as Month,
i.Year,
i.DateOpened
FROM
(
VALUES ('Janurary'), ('February'), ('March'),
('April'), ('May'), ('June'),
('July'), ('August'), ('September'),
('October'), ('November'), ('December')
) AS m(Name)
LEFT JOIN
(
SELECT
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS i ON i.Month = m.Name
and replace it in the dynamic query.
Updated SQL Fiddle
This will give you the missing months with zeros results:
| Month | 2014 | 2015 | 2014 | 2015 |
|-----------|------|------|------|------|
| Janurary | 0 | 0 | 0 | 0 | <<
| February | 0 | 1 | 0 | 1 |
| March | 0 | 0 | 0 | 0 | <<
| April | 0 | 3 | 0 | 3 |
| May | 0 | 0 | 0 | 0 | <<
| June | 0 | 0 | 0 | 0 | <<
| July | 1 | 1 | 1 | 1 |
| August | 1 | 1 | 1 | 1 |
| September | 1 | 0 | 1 | 0 |
| October | 0 | 0 | 0 | 0 | <<
| November | 0 | 0 | 0 | 0 | <<
| December | 1 | 0 | 1 | 0 |
A simple create table something like this..
CREATE TABLE #tmpIncidents
(
IncidentName NVARCHAR(50)
, DateOpened DATETIME
)
INSERT INTO #tmpIncidents (IncidentName,DateOpened) VALUES
('Test1',GETDATE()),
('Test1',DATEADD(YEAR,-1,GETDATE())),
('Test1',DATEADD(YEAR,-2,GETDATE())),
('Test1',DATEADD(YEAR,2,GETDATE())),
('Test1',DATEADD(YEAR,1,GETDATE())),
('Test1',DATEADD(YEAR,3,GETDATE()))
Then a build up query for multiple years
DECLARE #columnVar NVARCHAR(4000)
SELECT #columnVar =
(SELECT DISTINCT
'[' + CONVERT(NVARCHAR(150),DATEPART(YEAR,DateOpened)) + '],' AS [text()]
FROM #tmpIncidents
FOR XML PATH('')
)
SET #columnVar = (SELECT LEFT(#columnVar,LEN(#columnVar)-1))
--SELECT #columnVar --so you can see how it looks..
Then execute your query.
EXEC ('
SELECT
pv.*
FROM
(
SELECT DATENAME(MONTH,DateOpened) AS [DateName], DATEPART(YEAR,DateOpened) AS [YEAR], IncidentName FROM #tmpIncidents
) src
PIVOT
(
COUNT(IncidentName)
FOR [YEAR] IN (' + #columnVar + ')
) pv;
')

weekly aggregate with CTE not behaving as expected

I have this USERS table with users that can be of two different types (A and B). I need to show a report with the aggregate per type for each week. The query I have so far works well except some weeks are not grouping properly. In the example below, the week starting Jan 28th should have one line, not two.
Week Starts |Week| Type A | Type B
------------+----+--------+------
2013-02-04 | 14 | 2 | 26
2013-01-28 | 13 | 5 | 191
2013-01-28 | 13 | 0 | 24
2013-01-21 | 12 | 1 | 134
2013-01-21 | 12 | 0 | 20
2013-01-14 | 11 | 1 | 143
2013-01-14 | 11 | 0 | 2
2013-01-07 | 10 | 0 | 233
2013-01-07 | 10 | 0 | 23
2012-12-31 | 9 | 0 | 12
2012-12-31 | 9 | 4 | 164
2012-12-31 | 9 | 0 | 20
SQL
;with cte as
(
select DATEADD(m,-3,GETDATE()) firstday, DATEADD(m,-3,GETDATE()) + 6 - DATEDIFF(day, 0, DATEADD(m,-3,GETDATE())) %7 lastday, 1 week
union all
select lastday + 1, case when GETDATE() < lastday + 7 then GETDATE() else lastday + 7 end, week + 1
from cte
where lastday < GETDATE()
)
SELECT
cast(firstday as date) 'Week Starts',
cte.week as 'Week',
Sum(CASE WHEN USR_TYPE = 'A' THEN 1 ELSE 0 END) As 'Type A',
Sum(CASE WHEN USR_TYPE = 'B' THEN 1 ELSE 0 END) As 'Type B'
FROM cte left join USERS
ON cte.firstday <= USERS.CREATED
AND cte.lastday > USERS.CREATED
GROUP BY cte.week, cte.firstday, cte.lastday, DATEPART(YEAR,USERS.CREATED), DATEPART(wk,USERS.CREATED)
ORDER BY week desc
What am I doing wrong?
Without seeing any data from your users table I am going to take a guess.
The list of dates you are generating in the CTE includes the time.
You might need to cast() your firstday and lastday values as either a date or generate the list with no time.
See a SQL Fiddle Demo
Sample from your CTE and the new dates cast:
| CASTFIRSTDAY | CASTLASTDAY | WEEK | FIRSTDAY | LASTDAY |
---------------------------------------------------------------------------------------------------------
| 2012-11-05 | 2012-11-11 | 1 | November, 05 2012 20:08:10+0000 | November, 11 2012 20:08:10+0000 |
| 2012-11-12 | 2012-11-18 | 2 | November, 12 2012 20:08:10+0000 | November, 18 2012 20:08:10+0000 |
| 2012-11-19 | 2012-11-25 | 3 | November, 19 2012 20:08:10+0000 | November, 25 2012 20:08:10+0000 |
| 2012-11-26 | 2012-12-02 | 4 | November, 26 2012 20:08:10+0000 | December, 02 2012 20:08:10+0000 |
| 2012-12-03 | 2012-12-09 | 5 | December, 03 2012 20:08:10+0000 | December, 09 2012 20:08:10+0000 |
| 2012-12-10 | 2012-12-16 | 6 | December, 10 2012 20:08:10+0000 | December, 16 2012 20:08:10+0000 |
You might want to edit your CTE to return the date only values:
;with cte as
(
select
cast(DATEADD(m,-3,GETDATE()) as date) firstday,
cast(DATEADD(m,-3,GETDATE()) + 6 - DATEDIFF(day, 0, DATEADD(m,-3,GETDATE())) %7 as DATE) lastday,
1 week
union all
select
cast(DATEADD(DAY, 1, lastday) as date),
case
when cast(GETDATE() as date) < cast(DATEADD(DAY, 7, lastday) as date)
then cast(GETDATE() as date)
else cast(DATEADD(DAY, 7, lastday) as date)
end,
week + 1
from cte
where cast(lastday as date) < cast(GETDATE() as date)
)
select *
from cte
See SQL Fiddle with Demo