Related
I need to get users visits duration for each day in MySQL.
I have table like:
user_id,date,time_start, time_end
1, 2018-09-01, 09:00:00, 12:30:00
2, 2018-09-01, 13:00:00, 15:10:00
1, 2018-09-03, 09:30:00, 12:30:00
2, 2018-09-03, 13:00:00, 15:10:00
and need to get:
user_id,2018-09-01_duration,2018-09-03_duration
1,03:30:00,03:00:00
2,02:10:00,02:10:00
So columns need to be dynamic as some dates can be missed (2018-09-02).
Is it possible to do with one query without explicit joins per each day (as some days can be null)?
Update #1
Yes, I can generate columns in application side, But I still have terrible query like
SELECT user_id, d1.dt AS "2018-08-01_duration", d2.dt AS "2018-08-03_duration"...
FROM (SELECT
user_id,
time_format(TIMEDIFF(TIMEDIFF(time_out,time_in),time_norm),"%H:%i") AS dt
FROM visits
WHERE date = "2018-09-01") d1
LEFT JOIN(
SELECT
user_id,
time_format(TIMEDIFF(TIMEDIFF(time_out,time_in),time_norm),"%H:%i") AS dt
FROM visits
WHERE date = "2018-09-03") d3
ON users.id = d3.user_id...
Update #2
Yes, data like
select user_id, date, SEC_TO_TIME(SUM(TIME_TO_SEC(time_out) - TIME_TO_SEC(time_in))) as total
from visits
group by user_id, date;
is correct, but in this case data for users goes consistently. And I hope there's the way when I have rows with users and columns with dates (like in example above)
Try something like this:
select user_id, date, sum(time_end - time_start)
from table
group by user_id, date;
You will need to do some tweaking, as you didn't mention the RDBMS provider, but it should give you a clear idea on how to do it.
There's no dynamic way to use pivotting in MySQL but you might use the following for your case :
create table t(user_id int, time_start timestamp, time_end timestamp);
insert into t values(1,'2018-09-01 09:00:00', '2018-09-01 12:30:00');
insert into t values(2,'2018-09-01 13:00:00', '2018-09-01 15:10:00');
insert into t values(1,'2018-09-03 09:30:00', '2018-09-03 12:30:00');
insert into t values(2,'2018-09-03 13:00:00', '2018-09-03 15:10:00');
select min(q.user_id) as user_id,
min(CASE WHEN (q.date='2018-09-01') THEN q.time_diff END) as '2018-09-01_duration',
min(CASE WHEN (q.date='2018-09-03') THEN q.time_diff END) as '2018-09-03_duration'
from
(
select user_id, date(time_start) date,
concat(concat(lpad(hour(timediff(time_start, time_end)),2,'0'),':'),
concat(lpad(minute(timediff(time_start, time_end)),2,'0'),':'),
lpad(second(timediff(time_start, time_end)),2,'0')) as time_diff
from t
) q
group by user_id;
If you know the dates that you want in the result set, you don't need a dynamic query. You can just use conditional aggregation:
select user_id,
SEC_TO_TIME(SUM(CASE WHEN date = '2018-09-01' THEN TIME_TO_SEC(time_out) - TIME_TO_SEC(time_in))) as total_20180901,
SEC_TO_TIME(SUM(CASE WHEN date = '2018-09-02' THEN TIME_TO_SEC(time_out) - TIME_TO_SEC(time_in))) as total_20180902,
SEC_TO_TIME(SUM(CASE WHEN date = '2018-09-03' THEN TIME_TO_SEC(time_out) - TIME_TO_SEC(time_in))) as total_20180903
from visits
group by user_id;
You only need dynamic SQL if you don't know the dates you want in the result set. In that case, I would suggest following the same structure with the dates that you do want.
By the query you can solve your problem. the query is dynamic and you can improve it.
i use TSQL for the query, you can use the idea in MySQL.
declare
#columns as nvarchar(max),
#query as nvarchar(max)
select
#columns =
stuff
((
select
distinct
',' + quotename([date])
from
table_test
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 1, '')
--select #columns
set #query =
'with
cte_result
as
(
select
[user_id] ,
[date] ,
time_start ,
time_end ,
datediff(minute, time_start, time_end) as duration
from
table_test
)
select
[user_id], ' + #columns + '
from
(
select
[user_id] ,
[date] ,
duration
from
cte_result
)
sourceTable
pivot
(
sum(duration)
for [date] in (' + #columns + ')
)
pivotTable'
execute(#query)
I have table 1 with 3 columns id, startdate and enddate. With order id being the primary key how do I list the dates between the date range Startdate and Enddate?
What I have:
id Startdate EndDate
1 2/11/2014 2/13/2014
2 2/15/2014 2/17/2014
What I need:
id Date
1 2/11/2014
1 2/12/2014
1 2/13/2014
2 2/15/2014
2 2/16/2014
2 2/17/2014
How do I do this?
Use recursive CTE:
WITH tmp AS (
SELECT id, StartDate AS [Date], EndDate
FROM MyTable
UNION ALL
SELECT tmp.id, DATEADD(DAY,1,tmp.[Date]), tmp.EndDate
FROM tmp
WHERE tmp.[Date] < tmp.EndDate
)
SELECT tmp.ID, tmp.[Date]
FROM tmp
ORDER BY tmp.id, tmp.[Date]
OPTION (MAXRECURSION 0) -- For long intervals
If you have to use cursor/loop, most times you are doing it wrong.
If you do a one-off setup of an auxiliary calendar table as shown at Why should I consider using an auxiliary calendar table?, possibly omitting a lot of the columns if you don't need them, like this:
CREATE TABLE dbo.Calendar
(
dt SMALLDATETIME NOT NULL
PRIMARY KEY CLUSTERED,
Y SMALLINT,
M TINYINT,
D TINYINT
)
GO
SET NOCOUNT ON
DECLARE #dt SMALLDATETIME
SET #dt = '20000101'
WHILE #dt < '20300101'
BEGIN
INSERT dbo.Calendar(dt) SELECT #dt
SET #dt = #dt + 1
END;
UPDATE dbo.Calendar SET
Y = YEAR(dt),
M = MONTH(dt),
D = DAY(dt);
(You may well not need the Y, M, D columns at all, but I left those in to show that more data can be stored for fast access - the article I linked to shows how that could be used.)
Then if your table is named "so", your code would simply be
SELECT A.id, C.dt
FROM so AS A
JOIN Calendar AS C
ON C.dt >= A.StartDate AND C.dt<= A.EndDate
An advantage of using an auxiliary table like that is that your queries can be faster: the work done in setting one up is a one-time cost which doesn't happen during usage..
Instead of using CTE (to over come recursive and performance when date range is large) below query can be used to get the list of dates between two date range.
DECLARE #StartDateSTR AS VARCHAR(32); DECLARE #EndDateSTR AS
VARCHAR(32); DECLARE #EndDate AS DATE; DECLARE #StartDate AS DATE;
SET #StartDateSTR = '01/01/1990'; SET #EndDateSTR = '03/31/2025'; SET
#StartDate = CAST(#StartDateSTR AS date); SET #EndDate =
cast(#EndDateSTR AS date); SELECT
DATEADD(DAY, n1.rn - 1, #StartDate) AS dt FROM (SELECT rn=Row_number() OVER( ORDER BY (SELECT NULL)) FROM sys.objects a
CROSS JOIN sys.objects b CROSS JOIN sys.objects c CROSS JOIN
sys.objects d) as n1 WHERE n1.[rn] <= Datediff(dd, #StartDate,
#EndDate)+1;
here is my table namely book_master having following detail
id book_nmae book_cost publish_date
6 maths 150.0000 1992-08-06
7 science 120.0000 1992-08-07
8 gujrati 100.0000 1992-08-08
9 english 105.0000 1992-08-09
10 social study 100.0000 1992-08-10
here i want to get diffrence between publish_date 1992-08-06 and 1992-08-07 using book_name column.
i have tried below conditions to sql
SELECT DATEDIFF(DAY,book_master.publish_date,book_master.publish_date)
from book_master where book_name in('science','maths')
select DATEDIFF(DAY,book_master.publish_date.book_name('maths'),book_master.publish_date.book_name('science'))
from book_master
regards
rajdeep parmar
DECLARE #Date1 AS DATETIME
DECLARE #Date2 AS DATETIME
SELECT #Date1 = publish_date
FROM book_master
WHERE book_name = 'science'
SELECT #Date2 = publish_date
FROM book_master
WHERE book_name = 'maths'
SELECT DATEDIFF(DAY,#Date1,#Date2)
Another solution would be
SELECT DATEDIFF(DAY,T1.publish_date,T2.publish_date)
FROM
(
SELECT publish_date
FROM book_master
WHERE book_name = 'science'
) T1
CROSS JOIN
(
SELECT publish_date
FROM book_master
WHERE book_name = 'maths'
) T2
Instead of searching books by name I would use the id assuming that those id's are unique.
Solution:
DECLARE #MyTable TABLE(
id INT PRIMARY KEY, -- this PK constraint ensures unique values for this column
book_name NVARCHAR(200) NOT NULL,
publish_date DATE NOT NULL
);
INSERT #MyTable(id,book_name,publish_date)
VALUES
(6 ,'maths ', '1992-08-06'),
(7 ,'science ', '1992-08-07'),
(8 ,'gujrati ', '1992-08-08'),
(9 ,'english ', '1992-08-09'),
(10,'social study', '1992-08-10');
DECLARE #book_id_1 INT,
#book_id_2 INT;
SELECT #book_id_1=6,
#book_id_2=7;
SELECT DATEDIFF(DAY,pvt.[1],pvt.[2]) AS DaysDiff
FROM(
SELECT t.publish_date,
ROW_NUMBER() OVER(ORDER BY t.publish_date) AS RowNum
FROM #MyTable t
WHERE t.id IN (#book_id_1,#book_id_2)
) x
PIVOT(MAX(x.publish_date) FOR x.RowNum IN ([1],[2])) pvt;
Result:
DaysDiff
-----------
1
I am trying to write a SQL query that will turn this table:
Start_time End_time Instructor Student
9:00 9:35 Joe Bob Andrew
9:00 9:35 Joe Bob Smith
9:00 9:35 Roberto Andy
10:00 10:35 Joe Bob Angelica
11:00 11:20 Roberto Bob
Into something like this:
Instructor 9:00 10:00 11:00
Joe Bob Andrew, Smith Angelica NULL
Roberto Andy NULL Bob
I think that this is some sort of PIVOT command but I am not sure how I should go about writing the SQL query. The times are all dynamically generated so I would prefer it if the query would generate the column names in the second table dynamically (for example, if the original table contained an additional start time of 11:30, there should be a new column for 11:30 in the result).
Thank you in advance, I've been playing with this for a while but couldn't get it to work on my own. I can provide the SQL INSERT commands to give you the full data if necessary.
EDIT: It would be particularly helpful to get the result of this select statement as a VIEW. Thanks!
EDIT 2:
The code that is generating the view that makes the first table is:
CREATE VIEW schedule_view AS SELECT RTRIM(SUBSTRING(students.schedule_first_choice, 1, 5)) AS start_time, RTRIM(SUBSTRING(students.schedule_first_choice, -10, 5) AS end_time, CONCAT(instructors.first_name, ' ', instructors.last_name) AS instructor_name,
CONCAT(students.first_name, ' ', students.last_name) AS student_name , students.swim_america_level as class
FROM students, instructors WHERE students.instructor_id = instructors.instructor_id AND students.season =
(SELECT constant_value FROM constants WHERE constant_name = 'season') AND students.year =
(SELECT constant_value FROM constants WHERE constant_name = 'year')
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'GROUP_CONCAT(case when Start_time = ''',
Start_time,
''' then Student ELSE NULL end) AS ',
CONCAT('`',Start_time,'`')
)
) INTO #sql
FROM Table1;
SET #sql = CONCAT('SELECT Instructor, ', #sql, '
FROM Table1
GROUP BY Instructor');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle Demo
IN THESE ALL QUERY EXECUTED AND TESTED IN SQL SEREVR
USING STATIC COLUMNS IN PIVOT
Select * from
(
select Instructor,Start_time, STUFF((select ',' + student from Table1 a where
a.Start_time = b.Start_time and
a.Instructor=b.Instructor for xml path('')),1,1,'') as student
from table1 b ) x
PIVOT
(
max(Student)
for start_time IN ([9:00],[10:00], [11:00])
) p
DYNAMICALLY CREATE PIVOT TABLE Using Temp Table
Declare #tab nvarchar(max)
Declare #pivottab nvarchar(max)
Create table #Table2 (
instructor varchar(100),
student varchar(100),
start_time varchar(10)
);
insert into #Table2 (instructor,student,start_time)
select
Instructor,
STUFF((Select ','+student
from Table1 as a
Where
a.Start_time = b.Start_time and
a.Instructor=b.Instructor
for xml path('')),1,1,''),
Start_time
from table1 as b
select #tab = coalesce(#tab + ',['+start_time+']' ,'['+start_time+']')
from #Table2
group by Start_time
set #pivottab =
N' select * from (
select Instructor, Start_time, student from #Table2
) x
PIVOT (
max(Student) for start_time IN ('+#tab+')
) p'
execute(#pivottab)
if exists(select * from #table2)
Begin
Drop table #table2
End
DYNAMICALLY CREATE PIVOT TABLE
Declare #tab nvarchar(max)
Declare #pivottab nvarchar(max)
Select #tab = coalesce(#tab + ',['+start_time+']' , '['+start_time+']') from Table1
group by Start_time
set #pivottab = N'
select *
from
(
select Instructor,Start_time,STUFF((select '+ char(39)+char(44)+char(39) + '+ a.student from Table1 as a
where a.Start_time = b.Start_time and a.Instructor=b.Instructor for xml path('''')),1,1,'''')
as Student from table1 as b
) x
PIVOT
(
max(Student)
for start_time IN ('+#tab+')
) p'
execute(#pivottab)
I have a SQL table like this:
CC Descr C_NO Vol Wt
2050 Des1 123 20 40
2060 Des2 123 30 50
2050 Des1 125 20 40
2060 Des2 125 30 50
2050 Des1 126 20 40
and I want output like this:
2050
Des1
123
20
40
125
20
40
126
20
40
2060
Des2
123
30
50
125
30
50
How can I do that using TSQL code?
For every similar CC value which always have similar Descr value, it shows all the C_No, Vol and Wt values related to that particular CC value in the sequence written in the output section.
Try this
DECLARE #t TABLE
(
CC BIGINT,
Descr NVARCHAR(10),
C_NO INT,
Vol SMALLINT,
WT SMALLINT
)
INSERT INTO #t(CC,Descr,C_NO,Vol,WT)
VALUES (2050,'Des1',123,20,40)
,(2060,'Des2',123,30,50)
,(2050,'Des1',125,20,40)
,(2060,'Des2',125,30,50)
,(2050,'Des1',126,20,40)
;WITH CTE1 AS(
SELECT
t1.CC
,t1.Descr
,MergedColumn = STUFF(
(SELECT
','
+ CAST(C_NO AS VARCHAR(10)) + '/' + CAST(Vol AS VARCHAR(10)) + '/' + CAST(Wt AS VARCHAR(10))
FROM #t AS t2
WHERE t2.CC=t1.CC AND t2.Descr=t2.Descr
FOR XML PATH('')),1,1,'')
FROM #t t1
GROUP BY t1.CC,t1.Descr)
,CTE2 AS
(
SELECT
X.CC
,X.Descr
,Y.SplitDataByComma
FROM
(
SELECT
*,
CAST('<X>'+REPLACE(F.MergedColumn,',','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM CTE1 F
)X
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') AS SplitDataByComma
FROM X.xmlfilter.nodes('X') AS fdata(D)
)Y
)
,CTE3 AS
(
SELECT
X.CC
,X.Descr
,Y.SplitDataBySlash
, X.SplitDataByComma AS GrpID
,ROW_NUMBER() OVER( PARTITION BY X.SplitDataByComma ORDER BY X.CC,X.Descr ) AS Rn
,X.SplitDataByComma + CAST(CC AS Varchar(200)) + CAST(Descr AS Varchar(200)) CC_Descr
FROM
(
SELECT
*,
CAST('<X>'+REPLACE(F.SplitDataByComma,'/','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM CTE2 F
)X
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') AS SplitDataBySlash
FROM X.xmlfilter.nodes('X') AS fdata(D)
)Y
)
,CTE4 AS
(
SELECT
Rn = ROW_NUMBER() OVER(PARTITION BY CC ORDER BY CC)
,CC
,Descr
,CASE WHEN Rn = 1 THEN CAST (SplitDataBySlash AS VARCHAR(10)) ELSE ' ' END C_NO
,CASE WHEN Rn = 1 THEN ' ' ELSE CAST (SplitDataBySlash AS VARCHAR(10)) END Vol_Wt
,GrpID
FROM Cte3
)
,CTE5 AS(
SELECT
CC = CASE WHEN Rn > 1 THEN ' ' ELSE CAST(CC AS Varchar(200)) END
,Descr = CASE WHEN Rn > 1 THEN ' ' ELSE CAST(Descr AS Varchar(200)) END
,C_NO
,Vol_Wt
,GrpID
FROM Cte4)
SELECT
CHAR(10)
+ REPLICATE(SPACE(1),10)
+ CAST(CC as VARCHAR(100))
+ CHAR(10)
+ REPLICATE(SPACE(1),15)
+ Descr
+ CHAR(10)
+ REPLICATE(SPACE(1),10)
+ C_NO
+ CHAR(10)
+ REPLICATE(SPACE(1),15)
+ Vol_Wt
FROM CTE5
In Text Mode(CTRL + T), the result is
2050
Des1
123
20
40
125
20
40
126
20
40
2060
Des2
123
30
50
125
30
50
And in Grid Mode(CTRL + D) , the result is
(No column name)
2050
Des1
123
20
40
125
20
40
126
20
40
2060
Des2
123
30
50
125
30
50
However, SQL Server (or any database) is not the place to do formatting as others spoke about.Please look into the matter.
I wouldn't do this. TSQL is used to get the data, then you format that data with application code.
But if you're working in Query analyzer and just want the output there, you can try the following:
DECLARE mytable_cursor CURSOR FOR
SELECT CC, Descr, C_NO, Vol, Wt
FROM myTable
ORDER BY CC, Descr, C_NO
OPEN mytable_cursor;
FETCH NEXT FROM mytable_cursor
INTO #CC, #Descr, #C_NO, #Vol, #Wt
SET #oCC = #CC
SET #oDescr = #Descr
SET #oC_NO = #C_NO
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #CC;
WHILE ##FETCH_STATUS = 0 AND #oCC = #CC
BEGIN
PRINT ' ' + #Descr;
WHILE ##FETCH_STATUS = 0 AND #oDescr = #Descr
BEGIN
PRINT ' ' + #C_NO;
WHILE ##FETCH_STATUS = 0 AND #oC_NO = #C_NO
BEGIN
PRINT ' ' + #Vol;
PRINT ' ' + #WT;
FETCH NEXT FROM mytable_cursor
INTO #CC, #Descr, #C_NO, #Vol, #Wt
END
SET #oC_NO = #C_NO
END
SET #oDescr = #Descr
END
SET #oCC = #CC
END
CLOSE mytable_cursor;
DEALLOCATE mytable_cursor;
Or you can use a GROUP BY ... WITH ROLLUP and CASE WHEN to get the results a single query.
Here's an example with dashes instead of spaces to make sure the formatting is visible:
SELECT (CASE
WHEN Descr IS NULL THEN CC
WHEN C_NO IS NULL THEN '----' + Descr
WHEN Vol IS NULL THEN '--' + C_NO
WHEN Wt IS NULL THEN '------' + Vol
ELSE '------' + Wt
END)
FROM
(SELECT CC, Descr, C_NO, Vol, Wt
FROM my
GROUP BY CC, Descr, C_NO, Vol, Wt
WITH ROLLUP) AS x
WHERE CC IS NOT NULL
ORDER BY CC, Descr, C_NO, Vol, Wt
You can't do this in tsql code. You need to do this sort of thing in the application code.
One of the best way to represent data in hierarchical order is using XML. Something more, often the use of XML is connected with great performance.
Using your example information and this peace of code:
;WITH DistinctValues (CC,Descr ) AS
(
SELECT DISTINCT CC,Descr
FROM #TableOne
)
SELECT (
SELECT CC AS "CC/#CC"
,Descr AS "CC/Descr"
,(
SELECT C_NO AS "C_NO/#C_NO",
Vol AS "C_NO/Vol",
Wt AS "C_NO/Wt"
FROM #TableOne AS Data
WHERE Data.CC=DV.CC AND Data.Descr=DV.Descr
FOR XML PATH(''),TYPE
) AS "CC"
FROM DistinctValues AS DV
FOR XML PATH(''),TYPE
)
FOR XML PATH('Source'),TYPE
I get the following result (in the form of one row return by the statement above):
<Source>
<CC CC="2050">
<Descr>Des1</Descr>
<C_NO C_NO="123">
<Vol>20</Vol>
<Wt>40</Wt>
</C_NO>
<C_NO C_NO="125">
<Vol>20</Vol>
<Wt>40</Wt>
</C_NO>
<C_NO C_NO="126">
<Vol>20</Vol>
<Wt>40</Wt>
</C_NO>
</CC>
<CC CC="2060">
<Descr>Des2</Descr>
<C_NO C_NO="123">
<Vol>30</Vol>
<Wt>50</Wt>
</C_NO>
<C_NO C_NO="125">
<Vol>30</Vol>
<Wt>50</Wt>
</C_NO>
</CC>
</Source>
Note that the XML functions in T-SQL combined with the XML language structure gives you a huge of possible variations of output data formats - you use one or many nodes, you can represent the the data as attributes or node text. Combine this as you like and as you feel it will be easiest to work with in your application level.
Here is the whole code (just copy and paste) that I have used to generated the previous XML structure:
DECLARE #TableOne TABLE
(
CC BIGINT,
Descr NVARCHAR(10),
C_NO INT,
Vol SMALLINT,
WT SMALLINT
)
INSERT INTO #TableOne(CC,Descr,C_NO,Vol,WT)
VALUES (2050,'Des1',123,20,40)
,(2060,'Des2',123,30,50)
,(2050,'Des1',125,20,40)
,(2060,'Des2',125,30,50)
,(2050,'Des1',126,20,40)
;WITH DistinctValues (CC,Descr ) AS
(
SELECT DISTINCT CC,Descr
FROM #TableOne
)
SELECT (
SELECT CC AS "CC/#CC"
,Descr AS "CC/Descr"
,(
SELECT C_NO AS "C_NO/#C_NO",
Vol AS "C_NO/Vol",
Wt AS "C_NO/Wt"
FROM #TableOne AS Data
WHERE Data.CC=DV.CC AND Data.Descr=DV.Descr
FOR XML PATH(''),TYPE
) AS "CC"
FROM DistinctValues AS DV
FOR XML PATH(''),TYPE
)
FOR XML PATH('Source'),TYPE
This works like a charm and it is tested on Microsoft SQL Server Management Studio 2012.