Retrieve The Second Date Value - sql

I am trying to get the second value based on date. Suppose, a user has three entries with date and the second date should be retrieved with the value as well. So my sample input is something like this:
UserId Date Amount
1001 2019-10-10 00:00:00.000 10000
1001 2018-01-01 00:00:00.000 20000
1001 2017-10-02 00:00:00.000 6000
1002 2017-10-10 00:00:00.000 1000
1002 2016-08-02 00:00:00.000 600
1003 2015-06-10 00:00:00.000 200
Expected output:
UserId Date Amount
1001 2018-01-01 00:00:00.000 20000
1002 2016-08-02 00:00:00.000 600
1003 2015-06-10 00:00:00.000 200
I hope, the above samples are informative enough to understand and tried the followings to make it work:
SELECT DISTINCT m.UserId, m.Amount FROM UserAmount m WHERE m.DatePosted =
(SELECT MAX(k.DatePosted) FROM UserAmount k WHERE
k.DatePosted < (SELECT MAX(p.DatePosted) FROM UserAmount p));
SELECT DISTINCT m.UserId, m.Amount FROM UserAmount m WHERE m.UserId IN (SELECT q.UserId FROM DetailsUser q) AND m.DatePosted =
(SELECT MAX(k.DatePosted) FROM UserAmount k WHERE k.UserId IN (SELECT r.UserId FROM DetailsUser r) AND
k.DatePosted < (SELECT MAX(p.DatePosted) FROM UserAmount p WHERE p.UserId IN (SELECT s.UserId FROM DetailsUser s)));
Unfortunately, I am getting result for the first id say 1001 from table as follows:
UserId Amount
1001 20000
Anything skipped or doing wrong in the query? Would expect some valuable suggestions to make it work.
Script:
USE [DbName]
GO
/****** Object: Table [dbo].[UserAmount] Script Date: 04/16/2019 23:42:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UserAmount](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [nvarchar](20) NULL,
[DatePosted] [datetime] NULL,
[Amount] [float] NULL,
CONSTRAINT [PK_UserAmount] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[UserAmount] ON
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (1, N'1001', CAST(0x0000AAE200000000 AS DateTime), 10000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (2, N'1001', CAST(0x0000A85B00000000 AS DateTime), 20000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (3, N'1001', CAST(0x0000A80000000000 AS DateTime), 6000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (4, N'1002', CAST(0x0000A80800000000 AS DateTime), 1000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (5, N'1002', CAST(0x0000A65600000000 AS DateTime), 600)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (6, N'1003', CAST(0x0000A4B300000000 AS DateTime), 200)
SET IDENTITY_INSERT [dbo].[UserAmount] OFF
/****** Object: Table [dbo].[DetailsUser] Script Date: 04/16/2019 23:42:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DetailsUser](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [nvarchar](20) NULL,
CONSTRAINT [PK_DetailsUser] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[DetailsUser] ON
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (1, N'1001')
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (2, N'1002')
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (3, N'1003')
SET IDENTITY_INSERT [dbo].[DetailsUser] OFF
N.B: Sample query could be done using any of the query languages - MS SQL or Oracle.

A simple way would be to use window functions and pick the second record.
Given your above setup:
SELECT s1.UserID, s1.Amount, s1.DatePosted
FROM (
SELECT du.UserID, ua.Amount, ua.DatePosted
, ROW_NUMBER() OVER ( PARTITION BY ua.UserID ORDER BY ua.DatePosted DESC ) AS rn
, COUNT(*) OVER ( PARTITION BY ua.UserID) AS theCount
FROM DetailsUser du
LEFT OUTER JOIN UserAmount ua ON du.userID = ua.UserID
) s1
WHERE s1.rn = 2 OR s1.theCount <=1
https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=7035366e57188a3508e7348f0fe0ce8b
That will work on SQL Server and Oracle, but unfortunately not on MySQL 5.x (since it didn't introduce window functions until 8). PostgreS has had window functions for a while. I'm not sure which other flavors of SQL have them, but the same functionality can be duplicate in standard SQL.

You can do this using apply as well:
select du.*
from DetailsUser du outer apply
(select du2.date
from DetailsUser du2
where du2.userid = du.userid
offset 1 fetch first 1 row only
)
where du2.date is null or du2.date = du.date;

Related

How to return "most populated","least populated" countries grouped by continent organized in a single table via SQL?

This is a variant of the SQLzoo tutorial.
'world' table contains fields
'population'(assigned to each country),
'name' (all countries) and
'continent' (assigned to each country).
Expected output is a table as shown below
Continent
Most_populous
Least_populous
Africa
Ghana
xyz
Asia
China
abc
I did try a complicated function as below, but was not able to get it to work due to "SQL error". Not sure why.
SELECT DISTINCT continent
, (SELECT x.name
FROM world x
WHERE x.population = (SELECT max(y.population)
FROM world y
WHERE x.continent = y.continent)) AS most_populous
, (SELECT z.name
FROM world z
WHERE z.population = (SELECT min(a.population)
FROM world a
WHERE a.continent=z.continent)) AS least_populous FROM world;
Is there an easier way to get the required output?
You can try this:
SELECT world.continent,
(SELECT x.name
FROM world x
WHERE x.population = (SELECT max(y.population)
FROM world y
WHERE x.continent = y.continent)
AND world.continent = x.continent
) AS most_populous,
(SELECT z.name
FROM world z
WHERE z.population = (SELECT min(a.population)
FROM world a
WHERE z.continent = a.continent)
AND world.continent = z.continent
) AS least_populous
FROM world
GROUP BY world.continent;
Thank you
Since I did not had the tables with me, I ended up creating one. It would be better if you could include table and data creation scripts in question. Second, this is going to be a fairly small table so I am not going to worry about performance. I would create a view based on this query and that will be good enough.
Table creation scripts:
USE [StackOverflow]
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CountryPopulation]') AND type in (N'U'))
DROP TABLE [dbo].[CountryPopulation]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CountryPopulation](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](max) NULL,
[Continent] [nvarchar](max) NULL,
[Population] [int] NULL,
CONSTRAINT [PK_CountryPopulation] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
USE [StackOverflow]
GO
SET IDENTITY_INSERT [dbo].[CountryPopulation] ON
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (1, N'C1', N'Asia', 100)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (2, N'C2', N'Asia', 200)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (3, N'C3', N'Asia', 300)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (4, N'C4', N'Europe', 100)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (5, N'C5', N'Europe', 200)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (6, N'C6', N'Europe', 300)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (7, N'C7', N'Africa', 100)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (8, N'C8', N'Africa', 200)
GO
INSERT [dbo].[CountryPopulation] ([ID], [Name], [Continent], [Population]) VALUES (9, N'C9', N'Africa', 200)
GO
SET IDENTITY_INSERT [dbo].[CountryPopulation] OFF
GO
Query to get result:
SELECT MinMax.Continent,
ForLeast.[name] AS LeastPopulous,
ForMost.[name] AS MostPopulous
FROM CountryPopulation ForMost JOIN
CountryPopulation ForLeast JOIN
( SELECT DISTINCT Continent,
MIN([population]) OVER(PARTITION BY continent) AS LeastPopulation,
MAX([population]) OVER(PARTITION BY continent) AS MaxPopulation
FROM CountryPopulation) MinMax
ON ForLeast.Continent = MinMax.Continent AND ForLeast.[Population] = MinMax.LeastPopulation
ON ForMost.Continent = MinMax.Continent AND ForMost.[Population] = MinMax.MaxPopulation
Note the results for Africa. There are 2 rows. It is possible for more than one country to have least or most population. You would want to think on how to handle that scenario.

Cartesian product between two tables when the second one doesn't have all records

I have 2 tables in SQL Server and I would like to get all possible combinations by their IDs even when the second one has no records, I would like to know if there is a better solution than mine.
My 2 tables are:
Companies (CompanyId, Tot1, Tot2) with 10 records
Motors (MotorId, CompanyId_FK, TotSales1, TotSales2) with 4 records
, here the script to create and populate them:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Companies](
[CompanyId] [int] IDENTITY(1,1) NOT NULL,
[Tot1] [int] NOT NULL,
[Tot2] [int] NOT NULL,
CONSTRAINT [PK_Companies] PRIMARY KEY CLUSTERED
(
[CompanyId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Motors] Script Date: 10/15/2021 9:18:31 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Motors](
[MotorId] [int] IDENTITY(1,1) NOT NULL,
[CompanyId] [int] NOT NULL,
[TotSales1] [int] NOT NULL,
[TotSales2] [int] NOT NULL,
CONSTRAINT [PK_Motors] PRIMARY KEY CLUSTERED
(
[MotorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Companies] ON
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (1, 300, 200)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (2, 400, 100)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (3, 500, 100)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (4, 600, 200)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (5, 700, 500)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (6, 800, 400)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (7, 900, 300)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (8, 50, 20)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (9, 80, 20)
GO
INSERT [dbo].[Companies] ([CompanyId], [Tot1], [Tot2]) VALUES (10, 40, 10)
GO
SET IDENTITY_INSERT [dbo].[Companies] OFF
GO
SET IDENTITY_INSERT [dbo].[Motors] ON
GO
INSERT [dbo].[Motors] ([MotorId], [CompanyId], [TotSales1], [TotSales2]) VALUES (1, 4, 35, 23)
GO
INSERT [dbo].[Motors] ([MotorId], [CompanyId], [TotSales1], [TotSales2]) VALUES (2, 5, 140, 70)
GO
INSERT [dbo].[Motors] ([MotorId], [CompanyId], [TotSales1], [TotSales2]) VALUES (3, 7, 200, 24)
GO
INSERT [dbo].[Motors] ([MotorId], [CompanyId], [TotSales1], [TotSales2]) VALUES (4, 9, 2, 1)
GO
SET IDENTITY_INSERT [dbo].[Motors] OFF
GO
ALTER TABLE [dbo].[Motors] WITH CHECK ADD CONSTRAINT [FK_Motors_Companies] FOREIGN KEY([CompanyId])
REFERENCES [dbo].[Companies] ([CompanyId])
GO
ALTER TABLE [dbo].[Motors] CHECK CONSTRAINT [FK_Motors_Companies]
GO
I need as result set a list of all possible combination containing a Motor and a Company even when there is no sales:
CompanyId
MotorId
Tot1
Tot2
TotSales1
TotSales2
4
1
600
200
35
23
4
2
600
200
NULL
NULL
4
3
600
200
NULL
NULL
4
4
600
200
NULL
NULL
I created a CTE by CROSS JOIN with only CompanyId and MotorId and then I joined the other 2 in this way:
;WITH myTable AS (
SELECT Companies.CompanyId, MotorId
FROM Companies CROSS JOIN Motors
GROUP BY Companies.CompanyId, MotorId)
SELECT myTable.*, Tot1, Tot2, TotSales1,TotSales2 FROM myTable
INNER JOIN Companies ON myTable.CompanyId = Companies.CompanyId
LEFT OUTER JOIN Motors ON myTable.CompanyId = Motors.CompanyId AND myTable.MotorId = Motors.MotorId
Is there a better solution? To avoid misunderstanding: I can't change the sequence of the fields to map the results with a C# class.
You were so close to the answer... just do not do the GROUP BY in your CTE and you will get the result that you want:
;WITH myTable AS (
SELECT Companies.CompanyId, MotorId
FROM Companies CROSS JOIN Motors
)
SELECT myTable.*, Tot1, Tot2, TotSales1,TotSales2 FROM myTable
INNER JOIN Companies ON myTable.CompanyId = Companies.CompanyId
LEFT OUTER JOIN Motors ON myTable.CompanyId = Motors.CompanyId AND myTable.MotorId = Motors.MotorId
I am not sure why you thought you needed the GROUP BY, but since you wanted all possible combinations there is no need for it.

SQL Recursive Count

I have two tables I am joining with the following structure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ContentDivider](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentId] [int] NULL,
[Name] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_ContentDivider] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[CustomPage] Script Date: 23-03-2020 17:46:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CustomPage](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ContentDividerId] [int] NOT NULL,
[Name] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_CustomPage] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
A ContentDivider can have n ContentDividers as Children and can have m CustomPages as children as well.
I want a View that counts the Display the current CustomDivider and the COunt for all the CustomPages as Children of the current ContentDivider.
My Test data:
SET IDENTITY_INSERT [dbo].[ContentDivider] ON
GO
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (1, NULL, N'TopLevel1')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (2, NULL, N'TopLevel2')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (3, NULL, N'TopLevel3')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (4, 1, N'SecondLevel1')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (5, 1, N'SecondLevel2')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (6, 1, N'SecondLevel3')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (7, 4, N'ThirdLevel1')
INSERT [dbo].[ContentDivider] ([Id], [ParentId], [Name]) VALUES (8, 4, N'ThirdLevel2')
GO
SET IDENTITY_INSERT [dbo].[ContentDivider] OFF
GO
SET IDENTITY_INSERT [dbo].[CustomPage] ON
GO
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (1, 1, N'Level1_1')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (2, 1, N'Level1_2')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (3, 2, N'Level1_3')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (4, 2, N'Level1_4')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (5, 4, N'Level1_5')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (6, 5, N'Level1_6')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (7, 7, N'Level1_7')
INSERT [dbo].[CustomPage] ([Id], [ContentDividerId], [Name]) VALUES (8, 8, N'Level1_8')
GO
SET IDENTITY_INSERT [dbo].[CustomPage] OFF
GO
And the View I want to extend:
SELECT dbo.ContentDivider.ParentId, dbo.ContentDivider.Name, dbo.ContentDivider.Id, COUNT(DISTINCT dbo.CustomPage.Id) AS CustomPageCount
FROM dbo.ContentDivider LEFT OUTER JOIN
dbo.CustomPage ON dbo.ContentDivider.Id = dbo.CustomPage.ContentDividerId
GROUP BY dbo.ContentDivider.ParentId, dbo.ContentDivider.Name, dbo.ContentDivider.Id
As for now the view counts the custompages directly underneath the contentdivider. I would like all the CustomPages as children counted.
Any suggestions?
The respected result would be:
View
this sounds like a perfect situation for recursive cte ;)
So, if I understood correctly, your expected result would be Toplevel1 with 6 pages and Toplevel 2 with 2 pages since all the other levels are somewhere beneath these two mentioned levels?
The cte might look something like this (maybe you habe to include the max recursion option):
WITH cte AS(
SELECT 1 lvl, ID AS ParentID, ID, Name
FROM dbo.ContentDivider cd
WHERE ParentId IS NULL
UNION ALL
SELECT c.lvl+1 AS lvl, c.ParentID, cd.ID, cd.Name
FROM dbo.ContentDivider cd
INNER JOIN cte c ON cd.ParentID = c.ID
)
SELECT c.ParentID, cd.Name, COUNT(DISTINCT cp.Id) AS CustomPageCount
FROM cte c
JOIN dbo.ContentDivider cd ON cd.ID = c.ParentID
LEFT OUTER JOIN dbo.CustomPage cp ON cp.ContentDividerId = c.id
GROUP BY c.ParentId, cd.Name
This leads to all pages being assigned to its top level.
See fiddle for details: http://sqlfiddle.com/#!18/f1a44/28/1
Edit: Since you need the details down to DividerID, I extended my example in the fiddle. First of all, I fetch the PageCount per ID in one cte and additionally the PageCount aggregated on level (ParentID and all its IDs) - this done you don't need the count and grouping in the following ctes.
In my query I then check, if my current rows ID is a top-level of any kind and assign the corresponding PageCount to this row.
WITH cteCnt AS(
SELECT cd.ID, COUNT(DISTINCT cp.Id) AS CustomPageCount
FROM dbo.ContentDivider cd
LEFT OUTER JOIN dbo.CustomPage cp ON cp.ContentDividerId = cd.id
GROUP BY cd.ID
),
cteTop AS(
SELECT cd.ID, COUNT(DISTINCT cp.Id) AS CustomPageCount
FROM dbo.ContentDivider cd
LEFT OUTER JOIN dbo.CustomPage cp ON cp.ContentDividerId = cd.id
GROUP BY cd.ID
UNION ALL
SELECT cd.ParentID, COUNT(DISTINCT cp.Id) AS CustomPageCount
FROM dbo.ContentDivider cd
LEFT OUTER JOIN dbo.CustomPage cp ON cp.ContentDividerId = cd.id
WHERE cd.ParentID IS NOT NULL
GROUP BY cd.ParentID
),
cteTopSum AS(
SELECT ID, SUM(CustomPageCount) AS CustomPageCount
FROM ctetop
GROUP BY ID
),
cte AS(
SELECT 1 lvl, cd.ID AS ParentID, cd.ID AS ParentIDx, cd.ID, cd.Name, cnt.CustomPageCount
FROM dbo.ContentDivider cd
INNER JOIN cteCnt cnt ON cnt.ID = cd.ID
WHERE ParentId IS NULL
UNION ALL
SELECT c.lvl+1 AS lvl, c.ParentID, cd.ParentID AS ParentIDx, cd.ID, cd.Name, cnt.CustomPageCount
FROM dbo.ContentDivider cd
INNER JOIN cteCnt cnt ON cnt.ID = cd.ID
INNER JOIN cte c ON cd.ParentID = c.ID
),
cteOut AS(
SELECT *
,SUM(CustomPageCount) OVER (PARTITION BY ParentID) x
,SUM(CustomPageCount) OVER (PARTITION BY ParentIDx) y
FROM cte c
)
SELECT CASE WHEN co.ParentIDx = co.ID THEN NULL ELSE co.ParentIDx END AS ParentID, co.ID, co.Name, CASE WHEN co.ID = co.ParentID THEN co.X ELSE ts.CustomPageCount END CustomPageCount
FROM cteOut co
LEFT JOIN cteTopSum ts ON ts.ID = co.ID
ORDER BY 1, 2
See new fiddle for details: http://sqlfiddle.com/#!18/f1a44/185/1
I'm mot sure, if there is a prettier / nicer way to solve this, but seemingly this seems to solve the problem.
However, I did NOT check if it works if any number of sublevels or whatsoever - if you find any issues, feel free to comment.

Employee Month-Wise Service Length Using Sql

I am trying to create employee unit-wise service length report from their joining date and have used the following query to do so:
SELECT o.UnitName, p.DeptName, COUNT(m.EmpId) AS cnt,
(SELECT COUNT(m.EmpId) FROM EmpInf m
WHERE m.Desg IN ('Jr. Operator', 'Operator') AND m.Active = 'Active' AND m.DeptId = 2
AND DATEDIFF(MONTH, m.Joindate, GETDATE()) BETWEEN 0 AND 6) AS '0 - 6 Months'
FROM EmpInf m
INNER JOIN Department k ON k.DeptId = m.DeptId
INNER JOIN Section l ON l.secId = m.SecID
INNER JOIN UnitInf o ON o.UnitID = l.UnitName
INNER JOIN Department p ON p.DeptId = m.DeptId
WHERE Desg IN ('Jr. Operator', 'Operator') AND Active = 'Active' AND p.DeptName = 'Production'
GROUP BY o.UnitName, p.DeptName
Expected output as below: (As unit 1 and 4 have entry in the year 2017 means during 0 - 6 months of the year 2017 and there will be many others like 7 - 12, 13 - 24 etc)
Currently getting this:
I guess, having issue with the query and would be glad to know if there are any changes or alternates to do so.
Below is the script:
USE [sample]
GO
/****** Object: Table [dbo].[UnitInf] Script Date: 05/11/2017 21:19:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UnitInf](
[UnitID] [int] IDENTITY(1,1) NOT NULL,
[UnitName] [nvarchar](100) NULL,
CONSTRAINT [PK_UnitInf] PRIMARY KEY CLUSTERED
(
[UnitID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[UnitInf] ON
INSERT [dbo].[UnitInf] ([UnitID], [UnitName]) VALUES (1, N'Unit-01')
INSERT [dbo].[UnitInf] ([UnitID], [UnitName]) VALUES (2, N'Unit-02')
INSERT [dbo].[UnitInf] ([UnitID], [UnitName]) VALUES (3, N'Unit-03')
INSERT [dbo].[UnitInf] ([UnitID], [UnitName]) VALUES (4, N'Unit-04')
INSERT [dbo].[UnitInf] ([UnitID], [UnitName]) VALUES (5, N'Unit-05')
SET IDENTITY_INSERT [dbo].[UnitInf] OFF
/****** Object: Table [dbo].[Section] Script Date: 05/11/2017 21:19:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Section](
[secId] [int] IDENTITY(1,1) NOT NULL,
[SecName] [nvarchar](100) NULL,
[UnitName] [int] NULL,
CONSTRAINT [PK_Section] PRIMARY KEY CLUSTERED
(
[secId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Section] ON
INSERT [dbo].[Section] ([secId], [SecName], [UnitName]) VALUES (1, N'B-001', 1)
INSERT [dbo].[Section] ([secId], [SecName], [UnitName]) VALUES (2, N'C-001', 2)
INSERT [dbo].[Section] ([secId], [SecName], [UnitName]) VALUES (3, N'B-002', 1)
INSERT [dbo].[Section] ([secId], [SecName], [UnitName]) VALUES (4, N'D-004', 4)
SET IDENTITY_INSERT [dbo].[Section] OFF
/****** Object: Table [dbo].[EmpInf] Script Date: 05/11/2017 21:19:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[EmpInf](
[EmpId] [int] IDENTITY(1,1) NOT NULL,
[DeptId] [int] NULL,
[SecID] [int] NULL,
[EmpName] [nvarchar](100) NULL,
[GrossSal] [float] NULL,
[Desg] [nvarchar](100) NULL,
[SkillBonus] [float] NULL,
[Active] [nvarchar](10) NULL,
[JoinDate] [datetime] NULL,
CONSTRAINT [PK_EmpInf] PRIMARY KEY CLUSTERED
(
[EmpId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[EmpInf] ON
INSERT [dbo].[EmpInf] ([EmpId], [DeptId], [SecID], [EmpName], [GrossSal], [Desg], [SkillBonus], [Active], [JoinDate]) VALUES (1, 2, 2, N'John', 10000, N'Operator', 2000, N'Active', CAST(0x0000A59F00000000 AS DateTime))
INSERT [dbo].[EmpInf] ([EmpId], [DeptId], [SecID], [EmpName], [GrossSal], [Desg], [SkillBonus], [Active], [JoinDate]) VALUES (2, 2, 2, N'Jack', 12000, N'Operator', 5000, N'Active', CAST(0x0000A5BC00000000 AS DateTime))
INSERT [dbo].[EmpInf] ([EmpId], [DeptId], [SecID], [EmpName], [GrossSal], [Desg], [SkillBonus], [Active], [JoinDate]) VALUES (3, 2, 4, N'Nick', 14000, N'Jr. Operator', 6000, N'Active', CAST(0x0000A75100000000 AS DateTime))
INSERT [dbo].[EmpInf] ([EmpId], [DeptId], [SecID], [EmpName], [GrossSal], [Desg], [SkillBonus], [Active], [JoinDate]) VALUES (4, 2, 4, N'Bruce', 15000, N'Operator', 7000, N'Active', CAST(0x0000A79000000000 AS DateTime))
INSERT [dbo].[EmpInf] ([EmpId], [DeptId], [SecID], [EmpName], [GrossSal], [Desg], [SkillBonus], [Active], [JoinDate]) VALUES (5, 2, 1, N'Willy', 16000, N'Jr. Operator', 8000, N'Active', CAST(0x0000A7B800000000 AS DateTime))
SET IDENTITY_INSERT [dbo].[EmpInf] OFF
/****** Object: Table [dbo].[Department] Script Date: 05/11/2017 21:19:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Department](
[DeptId] [int] IDENTITY(1,1) NOT NULL,
[DeptName] [nvarchar](100) NULL,
CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED
(
[DeptId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Department] ON
INSERT [dbo].[Department] ([DeptId], [DeptName]) VALUES (1, N'Admin')
INSERT [dbo].[Department] ([DeptId], [DeptName]) VALUES (2, N'Production')
SET IDENTITY_INSERT [dbo].[Department] OFF
I think that simple conditional aggregation is the best approach:
SELECT o.UnitName, p.DeptName, COUNT(m.EmpId) AS cnt,
SUM(CASE WHEN DATEDIFF(MONTH, m.Joindate, GETDATE()) BETWEEN 0 AND 6
THEN 1 ELSE 0
END) AS [0 - 6 Months]
FROM EmpInf m INNER JOIN
Department k
ON k.DeptId = m.DeptId INNER JOIN
Section l
ON l.secId = m.SecID INNER JOIN
UnitInf o
ON o.UnitID = l.UnitName INNER JOIN
Department p
ON p.DeptId = m.DeptId
WHERE Desg IN ('Jr. Operator', 'Operator') AND Active = 'Active' AND
p.DeptName = 'Production'
GROUP BY o.UnitName, p.DeptName
I haven't run your code, but it looks like you're hardcoding too much in your subquery.
(SELECT COUNT(m.EmpId) FROM EmpInf m
WHERE m.Desg IN ('Jr. Operator', 'Operator') AND m.Active = 'Active' AND m.DeptId = 2
AND DATEDIFF(MONTH, m.Joindate, GETDATE()) BETWEEN 0 AND 6) AS '0 - 6 Months'
Problem 1: your alias for EmpInf in the subquery is "m" and your alias for EmpInf in your main query is "m". Make them different so you can link them.
Problem 2: Connect your variables in the subquery to values in the main query. So:
(SELECT COUNT(m.EmpId) FROM EmpInf subm
WHERE subm.Desg=m.Desg AND subm.Active = m.Active AND subm.DeptId = m.DeptId
AND DATEDIFF(MONTH, subm.Joindate, GETDATE()) BETWEEN 0 AND 6) AS '0 - 6 Months'

SQL Server - Query to split time by count (overlapping offices)

I'm looking for some advice on the approach I should take with a query. I have a table (EMP) which stores employee details and working hours for this year (40 hours per week). A further 2 tables store the primary and secondary offices employees belong to. Since employees can move between offices, these are stored with dates.
I'm looking to return the number of working hours during the time the employee is in an office. If primary offices overlap with secondary offices for an employee, the hours should be split by the number of overlapping offices for the overlapping period only.
I attach sample DDL below.
-- Employee Table with hours for year 2014
CREATE TABLE [dbo].[EMP](
[EMP_ID] [int] NOT NULL,
[EMP_NAME] [varchar](255) NULL,
[EMP_FYHOURS] [float] NULL,
CONSTRAINT [PK_EMP] PRIMARY KEY CLUSTERED
(
[EMP_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
-- Employees and their primary offices
CREATE TABLE [dbo].[OFFICEPRIMARY](
[OFFICEPRIMARY_ID] [int] NOT NULL,
[OFFICEPRIMARY_NAME] [varchar](255) NULL,
[OFFICEPRIMARY_EMP_ID] [int] NOT NULL,
[OFFICEPRIMARY_START] [datetime] NULL,
[OFFICEPRIMARY_END] [datetime] NULL,
CONSTRAINT [PK_OFFICEPRIMARY] PRIMARY KEY CLUSTERED
(
[OFFICEPRIMARY_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[OFFICEPRIMARY] WITH CHECK ADD CONSTRAINT [FK_OFFICEPRIMARY_FK1] FOREIGN KEY([OFFICEPRIMARY_EMP_ID])
REFERENCES [dbo].[EMP] ([EMP_ID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[OFFICEPRIMARY] CHECK CONSTRAINT [FK_OFFICEPRIMARY_FK1]
GO
-- Employees and their secondary offices
CREATE TABLE [dbo].[OFFICESECONDARY](
[OFFICESECONDARY_ID] [int] NOT NULL,
[OFFICESECONDARY_NAME] [varchar](255) NULL,
[OFFICESECONDARY_EMP_ID] [int] NOT NULL,
[OFFICESECONDARY_START] [datetime] NULL,
[OFFICESECONDARY_END] [datetime] NULL,
CONSTRAINT [PK_OFFICESECONDARY] PRIMARY KEY CLUSTERED
(
[OFFICESECONDARY_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[OFFICESECONDARY] WITH CHECK ADD CONSTRAINT [FK_OFFICESECONDARY_FK1] FOREIGN KEY([OFFICESECONDARY_EMP_ID])
REFERENCES [dbo].[EMP] ([EMP_ID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[OFFICESECONDARY] CHECK CONSTRAINT [FK_OFFICESECONDARY_FK1]
GO
-- Insert sample data
INSERT INTO EMP (EMP_ID, EMP_NAME, EMP_FYHOURS)
VALUES (1, 'John Smith', 2080);
INSERT INTO EMP (EMP_ID, EMP_NAME, EMP_FYHOURS)
VALUES (2, 'Jane Doe', 2080);
GO
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (1, 'London', 1, '2014-01-01', '2014-05-31')
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (2, 'Berlin', 1, '2014-06-01', '2014-08-31')
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (3, 'New York', 1, '2014-09-01', '2014-12-31')
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (4, 'New York', 2, '2014-01-01', '2014-04-15')
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (5, 'Paris', 2, '2014-04-16', '2014-09-30')
INSERT INTO OFFICEPRIMARY (OFFICEPRIMARY_ID, OFFICEPRIMARY_NAME, OFFICEPRIMARY_EMP_ID, OFFICEPRIMARY_START, OFFICEPRIMARY_END)
VALUES (6, 'London', 2, '2014-10-01', '2014-12-31')
GO
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (1, 'Paris', 1, '2014-01-01', '2014-03-31')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (2, 'Lyon', 1, '2014-04-01', '2014-05-15')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (3, 'Berlin', 1, '2014-05-16', '2014-09-30')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (4, 'Chicago', 1, '2014-10-01', '2015-02-22')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (5, 'Chicago', 2, '2013-11-21', '2014-04-10')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (6, 'Berlin', 2, '2014-04-11', '2014-09-16')
INSERT INTO OFFICESECONDARY (OFFICESECONDARY_ID, OFFICESECONDARY_NAME, OFFICESECONDARY_EMP_ID, OFFICESECONDARY_START, OFFICESECONDARY_END)
VALUES (7, 'Amsterdam', 2, '2014-09-17', '2015-03-31')
GO
Thanks for the pointer. I adjusted your query so it presents a union of the primary and secondary office.
All that remains is working out the hours for overlapping periods between offices. For example,
John Smith, New York, 01/04/2014, 10/08/2014
John Smith, London, 01/08/2014, 31/12/2014
For the overlapping period between the offices which is 01/08/2014 to 10/08/2014, I would expect the hours to be split equally. If there were 3 overlapping offices, then it would be split 3-ways.
select 'Primary' as Office, e.EMP_NAME, op.OFFICEPRIMARY_NAME, op.OFFICEPRIMARY_START, op.OFFICEPRIMARY_END, datediff(wk,OFFICEPRIMARY_START,OFFICEPRIMARY_END) * 40 as HoursWorkedPrimary
from EMP e
inner join OFFICEPRIMARY op on op.OFFICEPRIMARY_EMP_ID = e.EMP_ID
union all
select 'Secondary' as Office, e.EMP_NAME, os.OFFICESECONDARY_NAME, os.OFFICESECONDARY_START, os.OFFICESECONDARY_END, datediff(wk,OFFICESECONDARY_START,OFFICESECONDARY_END) * 40 as HoursWorkedSecondary
from EMP e
inner join OFFICESECONDARY os on os.OFFICESECONDARY_EMP_ID = e.EMP_ID
order by e.EMP_NAME
If I understand correctly, the end result you want to see is the number of total hours worked per employee and office?
I've come up with this:
-- generate date table
declare #MinDate datetime, #MaxDate datetime
SET #MinDate = (SELECT MIN(d) FROM (SELECT d = OFFICEPRIMARY_START FROM dbo.OFFICEPRIMARY UNION SELECT OFFICESECONDARY_START FROM dbo.OFFICESECONDARY) a)
SET #MaxDate = (SELECT MAX(d) FROM (SELECT d = OFFICEPRIMARY_END FROM dbo.OFFICEPRIMARY UNION SELECT OFFICESECONDARY_END FROM dbo.OFFICESECONDARY) a)
SELECT
d = DATEADD(day, number, #MinDate)
INTO
#tmp_dates
FROM
(SELECT DISTINCT number FROM master.dbo.spt_values WHERE name IS NULL) n
WHERE
DATEADD(day, number, #MinDate) < #MaxDate
;WITH CTE AS
(
SELECT
d.d
,o.OfficeType
,o.OfficeID
,o.OfficeName
,o.EmpID
,EmpName = e.EMP_NAME
,HoursWorked = 8 / (COUNT(1) OVER (PARTITION BY EmpID, d))
FROM
(
SELECT
OfficeType = 1
,OfficeID = op.OFFICEPRIMARY_ID
,OfficeName = op.OFFICEPRIMARY_NAME
,EmpID = op.OFFICEPRIMARY_EMP_ID
,StartDate = op.OFFICEPRIMARY_START
,EndDate = op.OFFICEPRIMARY_END
FROM
dbo.OFFICEPRIMARY op
UNION
SELECT
OfficeType = 2
,OfficeID = os.OFFICESECONDARY_ID
,OfficeName = os.OFFICESECONDARY_NAME
,EmpID = os.OFFICESECONDARY_EMP_ID
,StartDate = os.OFFICESECONDARY_START
,EndDate = os.OFFICESECONDARY_END
FROM
dbo.OFFICESECONDARY os
) o
INNER JOIN
dbo.EMP e ON e.EMP_ID = o.EmpID
INNER JOIN
#tmp_dates d ON o.StartDate<=d.d AND o.EndDate>=d.d
)
SELECT
EmpID
,EmpName
,OfficeType
,OfficeName
,TotalHoursWorked = SUM(HoursWorked)
FROM
CTE
GROUP BY
EmpID
,EmpName
,OfficeType
,OfficeID
,OfficeName
ORDER BY
EmpID
,OfficeName
I first generate a temp table with the dates between minimum date and maximum date.
Then I union both office tables (why you have 2 tables anyway?) and I get a CTE that returns data on employee, date, office and number of hours worked in this office (8 divided by count of offices where employee has worked in on this day).
Then I sum this data to get sum of hours grouped by employee and office.
Maybe there is a simpler solution to this. This was the first solution that came to my mind.
This should give you a head start:
select datediff(wk,OFFICEPRIMARY_START,OFFICEPRIMARY_END) * 40 as HoursWorkedPrimary
,datediff(wk,OFFICESECONDARY_START,OFFICESECONDARY_END) * 40 as HoursWorkedSecondary
,EMP_NAME
,OFFICEPRIMARY_NAME,OFFICEPRIMARY_START,OFFICEPRIMARY_END
,OFFICESECONDARY_NAME,OFFICESECONDARY_START,OFFICESECONDARY_END
from [EMP]
inner join OFFICEPRIMARY as op on op.OFFICEPRIMARY_EMP_ID = EMP.EMP_ID
inner join OFFICESECONDARY as os on os.OFFICESECONDARY_EMP_ID = EMP.EMP_ID
The link below should help point you in the right direction to identifying how the dates overlap.
Count days in date range with set of exclusions which may overlap