Related
Given the following tables, I need to filter the things data based on which user is making the call as well as additional (optional) filters that consist of a user_id/role combination.
At any time a user should only receive results for things that they are linked to unless they have full access. The additional filters are AND filters meaning that the results should satisfy all the filters.
The parameters #user_id, #has_full_access and user_id/role filters are passed through using Dapper.
CREATE TABLE users
(
id int NOT NULL
CONSTRAINT PK_users PRIMARY KEY CLUSTERED (id)
)
CREATE TABLE user_roles
(
user_id int NOT NULL,
role varchar(10) NOT NULL
CONSTRAINT FK_user_roles_users FOREIGN KEY(user_id) REFERENCES users(id)
)
CREATE TABLE things
(
id int NOT NULL
CONSTRAINT PK_things PRIMARY KEY CLUSTERED (id)
)
CREATE TABLE thing_permissions
(
thing_id int NOT NULL,
user_id int NOT NULL,
role varchar(10) NOT NULL
CONSTRAINT FK_thing_permissions_things FOREIGN KEY(thing_id) REFERENCES things(id),
CONSTRAINT FK_thing_permissions_users FOREIGN KEY(user_id) REFERENCES users(id)
)
INSERT INTO users VALUES (1)
INSERT INTO users VALUES (2)
INSERT INTO users VALUES (3)
INSERT INTO users VALUES (4)
INSERT INTO users VALUES (5)
INSERT INTO user_roles VALUES (1, 'Admin')
INSERT INTO user_roles VALUES (2, 'Creator')
INSERT INTO user_roles VALUES (2, 'Owner')
INSERT INTO user_roles VALUES (3, 'Creator')
INSERT INTO user_roles VALUES (3, 'Owner')
INSERT INTO user_roles VALUES (4, 'Creator')
INSERT INTO user_roles VALUES (5, 'Owner')
INSERT INTO things VALUES (1)
INSERT INTO things VALUES (2)
INSERT INTO things VALUES (3)
INSERT INTO things VALUES (4)
INSERT INTO things VALUES (5)
INSERT INTO thing_permissions VALUES (1, 2, 'Creator')
INSERT INTO thing_permissions VALUES (1, 3, 'Creator')
INSERT INTO thing_permissions VALUES (1, 2, 'Owner')
INSERT INTO thing_permissions VALUES (2, 2, 'Creator')
INSERT INTO thing_permissions VALUES (2, 5, 'Owner')
INSERT INTO thing_permissions VALUES (3, 4, 'Creator')
INSERT INTO thing_permissions VALUES (3, 3, 'Owner')
INSERT INTO thing_permissions VALUES (3, 5, 'Owner')
INSERT INTO thing_permissions VALUES (4, 3, 'Creator')
INSERT INTO thing_permissions VALUES (4, 5, 'Owner')
INSERT INTO thing_permissions VALUES (5, 2, 'Creator')
The following are some examples of various input combinations as well as the expected results.
--Scenario 1:
--Expected Results: 1, 2, 3, 4, 5
DECLARE #user_id int = 1
DECLARE #has_full_access bit = 1
DECLARE #filters TABLE (user_id int, [role] varchar(10))
--Scenario 2:
--Expected Results: 1, 2, 5
DECLARE #user_id int = 2
DECLARE #has_full_access bit = 0
DECLARE #filters TABLE (user_id int, [role] varchar(10))
--Scenario 3:
--Expected Results: 1
DECLARE #user_id int = 1
DECLARE #has_full_access bit = 1
DECLARE #filters TABLE (user_id int, [role] varchar(10))
INSERT INTO #filters VALUES (2, 'Creator')
INSERT INTO #filters VALUES (2, 'Owner')
--Scenario 4:
--Expected Results: 3
DECLARE #user_id int = 1
DECLARE #has_full_access bit = 1
DECLARE #filters TABLE (user_id int, [role] varchar(10))
INSERT INTO #filters VALUES (3, 'Owner')
INSERT INTO #filters VALUES (5, 'Owner')
--Scenario 5:
--Expected Results: 1
DECLARE #user_id int = 2
DECLARE #has_full_access bit = 0
DECLARE #filters TABLE (user_id int, [role] varchar(10))
INSERT INTO #filters VALUES (3, 'Creator')
--Scenario 6:
--Expected Results: no results
DECLARE #user_id int = 1
DECLARE #has_full_access bit = 1
DECLARE #filters TABLE (user_id int, [role] varchar(10))
INSERT INTO #filters VALUES (2, 'Creator')
INSERT INTO #filters VALUES (4, 'Creator')
Here is a SQL Fiddle with the setup.
At the moment, I have the following function that returns all the things as well as the role(s) in which the user is linked.
FUNCTION GetMyThings (#user_id INT, #has_full_access BIT)
RETURNS TABLE
AS
RETURN
(
SELECT t.id, 'Admin' AS role
FROM things t
WHERE #has_full_access = 1
UNION
SELECT t.id, tp.role
FROM things t
INNER JOIN thing_permissions tp ON tp.thing_id = t.id
WHERE tp.user_id = #user_id
)
I use this function to get a list of things that the calling user has access to as well as those for each user in the filters. Finally I return the results that are in both these data sets.
DECLARE #my_things TABLE (id INT)
INSERT INTO #my_things SELECT id FROM GetMyThings(#user_id, #has_full_access)
DECLARE #filtered_things TABLE (id INT)
INSERT INTO #filtered_things SELECT ft.id FROM #filters f CROSS APPLY (SELECT DISTINCT id, role FROM GetMyThings(f.user_id, 0)) ft WHERE ft.role = f.role GROUP BY ft.id HAVING COUNT(ft.id) >= (SELECT COUNT(user_id) FROM #filters)
DECLARE #has_filter BIT = (SELECT has_filter = CASE WHEN (COUNT(user_id) > 0) THEN 1 ELSE 0 END FROM #filters)
DECLARE #final_things TABLE (id INT)
INSERT INTO #final_things SELECT id FROM #my_things WHERE #has_filter = 0 OR id IN (SELECT id FROM #filtered_things)
SELECT * FROM #final_things
Is there a better way of doing this? My solution works but with bigger data sets it seems as if the function slows the query down when compared to selecting from the original data.
I've also tried using views but because I need the #has_full_access parameter and separate SELECTs UNIONed together I cannot add a WHERE to each SELECT.
I have this script that creates a table and store Sale Order information. The scenario behind it is that once and Item is added into Sale Order it's Status is 'A' means Add. Later somehow customer wants that item to be removed so we add a new row with same details but Status as 'D' means Delete.
Now I want to get only active Sale Order Items which should not include that item which was Added and then Removed from Order.
Here's my script.
CREATE TABLE [dbo].[SALE_DETAIL](
[ORDER_NUMBER] [varchar](50) NULL,
[ITEM_NAME] [varchar](250) NULL,
[QUANTITY] [int] NULL,
[PRICE] [numeric](18, 0) NULL,
[Status] [varchar](50) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[SALE_DETAIL] ([ORDER_NUMBER], [ITEM_NAME], [QUANTITY], [PRICE], [Status]) VALUES (N'SO-100-ORD-19', N'Double Bed', 5, CAST(70000 AS Numeric(18, 0)), N'A')
GO
INSERT [dbo].[SALE_DETAIL] ([ORDER_NUMBER], [ITEM_NAME], [QUANTITY], [PRICE], [Status]) VALUES (N'SO-100-ORD-19', N'Sofa', 5, CAST(10000 AS Numeric(18, 0)), N'A')
GO
INSERT [dbo].[SALE_DETAIL] ([ORDER_NUMBER], [ITEM_NAME], [QUANTITY], [PRICE], [Status]) VALUES (N'SO-100-ORD-19', N'Dining Table', 1, CAST(50000 AS Numeric(18, 0)), N'A')
GO
INSERT [dbo].[SALE_DETAIL] ([ORDER_NUMBER], [ITEM_NAME], [QUANTITY], [PRICE], [Status]) VALUES (N'SO-100-ORD-19', N'Sofa', 5, CAST(10000 AS Numeric(18, 0)), N'D')
GO
The expected output I'm looking for should be something like this as Item 'Sofa' was cancelled from Order.
ORDER_NUMBER ITEM_NAME QTY PRICE
SO-100-ORD-19 Dining Table 1 50000
SO-100-ORD-19 Double Bed 5 70000
Query:
SELECT ORDER_NUMBER, ITEM_NAME, QUANTITY, PRICE FROM [dbo].[SALE_DETAIL]
WHERE Status <> 'D'
GROUP BY ORDER_NUMBER, ITEM_NAME, QUANTITY, PRICE
I would use NOT EXISTS:
SELECT SD.ORDER_NUMBER,
SD.ITEM_NAME,
SD.QUANTITY,
SD.PRICE
FROM dbo.[SALE_DETAIL] SD
WHERE NOT EXISTS (SELECT 1
FROM dbo.[SALE_DETAIL] e
WHERE e.ORDER_NUMBER = SD.ORDER_NUMBER
AND e.ITEM_NAME = SD.ITEM_NAME
AND e.[Status] = 'D');
Logically, the set-based answer is to use EXCEPT:
declare #SALE_DETAIL table([ORDER_NUMBER] [varchar](50) NULL,
[ITEM_NAME] [varchar](250) NULL,
[QUANTITY] [int] NULL,[PRICE] [numeric](18, 0) NULL,[Status] [varchar](50) NULL)
INSERT #SALE_DETAIL ([ORDER_NUMBER], [ITEM_NAME], [QUANTITY], [PRICE], [Status]) VALUES
(N'SO-100-ORD-19', N'Double Bed', 5, CAST(70000 AS Numeric(18, 0)), N'A'),
(N'SO-100-ORD-19', N'Sofa', 5, CAST(10000 AS Numeric(18, 0)), N'A'),
(N'SO-100-ORD-19', N'Dining Table', 1, CAST(50000 AS Numeric(18, 0)), N'A'),
(N'SO-100-ORD-19', N'Sofa', 5, CAST(10000 AS Numeric(18, 0)), N'D')
select Order_number,Item_name,Quantity,Price
from #SALE_DETAIL
where Status = 'A'
except
select Order_number,Item_name,Quantity,Price
from #SALE_DETAIL
where Status = 'D'
Which produces the results you've asked for. However, note that for whetever reason, this often seems to perform poorly in practice, in which case something like Larnu's Answer may be preferred.
In words: select all 'A'-records for which no matching 'D'-record exists.
In SQL:
SELECT ORDER_NUMBER, ITEM_NAME, QUANTITY, PRICE
FROM [dbo].[SALE_DETAIL] X
WHERE Status = 'A'
AND NOT EXISTS (
SELECT 1
FROM [dbo].[SALE_DETAIL] Y
WHERE Y.Status = 'D'
AND Y.ORDER_NUMBER = X.ORDER_NUMBER
AND Y.ITEM_NAME = X.ITEM_NAME
)
You can try get expected result using not in operator as shown below.
SELECT DISTINCT A.ORDER_NUMBER, A.ITEM_NAME,A.QUANTITY, A.PRICE, A.Status
FROM SALE_DETAIL A
where A.ITEM_NAME not in (select s.ITEM_NAME from SALE_DETAIL s
where s.[Status] = 'D')
The output is as shown below
ORDER_NUMBER ITEM_NAME QUANTITY PRICE Status
------------------------------------------------------
SO-100-ORD-19 Dining Table 1 50000 A
SO-100-ORD-19 Double Bed 5 70000 A
You can find the live demo Live Demo Here
Consider following table:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Product](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductCategory] [int] NOT NULL,
[ProductCategoryGuid] [uniqueidentifier] NULL,
CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED
(
[ProductID] 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].[Product] ON
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (1, 2, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (2, 2, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (3, 2, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (4, 3, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (5, 4, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (6, 2, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (7, 3, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (8, 4, NULL)
GO
INSERT [dbo].[Product] ([ProductID], [ProductCategory], [ProductCategoryGuid]) VALUES (9, 4, NULL)
GO
SET IDENTITY_INSERT [dbo].[Product] OFF
GO
Data looks like this:
ProductID ProductCategory ProductCategoryGuid
1 2 NULL
2 2 NULL
3 2 NULL
4 3 NULL
5 4 NULL
6 2 NULL
7 3 NULL
8 4 NULL
9 4 NULL
What I would like to achieve is to update [ProductCategoryGuid] column so that all rows with the same value for [ProductCategory] will have the same Guid value in [ProductCategoryGuid] column
To clarify:
Guid values will be generated as a part of the UPDATE query using NEWID() function
Rows with ProductID IN( 1, 2, 3, 6) will have Guid1
Rows with ProductID IN( 4,7) will have Guid2
Rows with ProductID IN( 5,8,9) will have Guid3
I would use following script that use a table variable to store list of distinct categories. The same table variable has a GUID column having as default value NEWID() function. At the end of the script there is an UPDATE statement using table variable as source and dbo.Product table as target:
DECLARE #Results TABLE (
[ProductCategory] [int] NOT NULL,
[ProductCategoryGuid] [uniqueidentifier] NOT NULL DEFAULT (NEWID())
)
INSERT #Results (ProductCategory)
SELECT DISTINCT p.ProductCategory
FROM dbo.Product p
UPDATE p
SET ProductCategoryGuid = r.ProductCategoryGuid
OUTPUT deleted.ProductCategoryGuid, inserted.ProductCategoryGuid
FROM dbo.Product p
INNER JOIN #Results r ON p.ProductCategory = r.ProductCategory
Comment OUTPUT clause if you don't want to see the old and new values.
Update: one statement solution (it requires SQL2012+)
;WITH CteUpdateProduct
AS (
SELECT *, FIRST_VALUE(NewGUID) OVER(PARTITION BY ProductCategory ORDER BY ProductID) AS NewProductCategoryGuid
FROM (
SELECT p.*, NEWID() AS NewGUID
FROM dbo.Product p
) x
)
UPDATE CteUpdateProduct
SET ProductCategoryGuid = NewProductCategoryGuid
OUTPUT inserted.ProductID, inserted.ProductCategory, inserted.ProductCategoryGuid;
WITH productCategories as (
SELECT DISTINCT ProductCategory
FROM product
), productCategoriesWithGuid as (
SELECT ProductCategory, NEWID() ProductCategoryGuid
From productCategories
)
UPDATE product
SET ProductCategoryGuid = pc.ProductCategoryGuid
FROM Product p
JOIN productCategoriesWithGuid pc on p.ProductCategory = pc.ProductCategory
This query gets the distinct ProductCategories,
Creates a GUID for each of them,
And lastly updates the product table with the GUIDs
All in one statement.
I want to create a temp table maybe using pivot for month and year as column.
I mentioned the question and solution for the pivot
Summarising the months data using pivot
CREATE TABLE [dbo].[Table1]([PID] [int] NULL,[ProductDesc] [nvarchar](50) NULL,[ProductCode] [nvarchar](10) NULL) ON [PRIMARY]
CREATE TABLE [dbo].[Table2]([Date] [varchar](50) NULL,[PID] [int] NULL) ON [PRIMARY]
---insert script---
INSERT [dbo].[Table1] ([PID], [ProductDesc], [ProductCode]) VALUES (1, N'Packet-Eye', N'P001')
INSERT [dbo].[Table1] ([PID], [ProductDesc], [ProductCode]) VALUES (2, N'Wiggy', N'W099 ')
INSERT [dbo].[Table1] ([PID], [ProductDesc], [ProductCode]) VALUES (3, N'Wimax-Lite', N'W001')
INSERT [dbo].[Table1] ([PID], [ProductDesc], [ProductCode]) VALUES (4, N'Wimax-Home', N'e W002 ')
INSERT [dbo].[Table2] ([Date], [PID]) VALUES (N'1/14/2009 ', 1)
INSERT [dbo].[Table2] ([Date], [PID]) VALUES (N'1/15/2009 ', 1)
INSERT [dbo].[Table2] ([Date], [PID]) VALUES (N'2/1/2009', 2)
INSERT [dbo].[Table2] ([Date], [PID]) VALUES (N'3/3/2009', 3)
GO
SELECT *
FROM
(
SELECT t1.productdesc as pd,COUNT(month(t2.date))as dates,
DateName( month , DateAdd( month , MONTH(t2.date) , 0 ) - 1 ) as mon
FROM table1 t1 inner join table2 t2 on t1.pid=t2.pid
where year(date) between 2009
and 2010 group by productdesc,month(t2.date),month (t2.date)
) AS D
PIVOT
(
sum(dates)
FOR mon IN( [January],[February],[March],[April],[May],[June],[July],[August],[September],[October],[November],[December])
) AS P
I have the following tables:
CREATE TABLE [dbo].[Amounts](
[EmpID] [int] NULL,
[Amount] [smallmoney] NULL
)
GO
CREATE TABLE [dbo].[Employees](
[LastName] [varchar](30) NULL,
[FirstName] [varchar](20) NULL,
[LocationID] [int] NULL,
[EmpID] [int] IDENTITY(1,1) NOT NULL
)
GO
CREATE TABLE [dbo].[Locations](
[LocationID] [int] NOT NULL,
[City] [varchar](20) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (7, 4750.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (2, 15750.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (7, 18100.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (4, 21000.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (3, 18100.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (10, 41000.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (7, 25000.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (11, 21500.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (5, 9900.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (7, 95900.0000)
GO
INSERT [dbo].[Amounts] ([EmpID], [Amount]) VALUES (9, 55000.0000)
GO
SET IDENTITY_INSERT [dbo].[Employees] ON
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Andrews', N'Alex', 1, 1)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Brown', N'Barry', 1, 2)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Jones', N'Lee', 2, 3)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Kendal', N'David', 1, 4)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Birch', N'Eric', 1, 5)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Kircher', N'Lisa', 4, 6)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Williams', N'David', 1, 7)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Marshall', N'John', NULL, 8)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Howard', N'James', 2, 9)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'O''Donnell', N'Terry', 2, 10)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Smythe', N'Sally', 1, 11)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Donovan', N'Barbara', 4, 12)
GO
INSERT [dbo].[Employees] ([LastName], [FirstName], [LocationID], [EmpID]) VALUES (N'Wagner', N'Phil', 1, 13)
GO
SET IDENTITY_INSERT [dbo].[Employees] OFF
GO
INSERT [dbo].[Locations] ([LocationID], [City]) VALUES (1, N'Paris')
GO
INSERT [dbo].[Locations] ([LocationID], [City]) VALUES (2, N'Sydney')
GO
INSERT [dbo].[Locations] ([LocationID], [City]) VALUES (3, N'Chicago')
GO
INSERT [dbo].[Locations] ([LocationID], [City]) VALUES (4, N'London')
GO
I want to generate XML which is ordered by the city which contains the highest overall amount first and then, nested within that, each employee (ordered by those with the highest total amount first) and then nested within each employee their amounts (highest first).
The following query (rather complex - please let me know if there is a simpler/cheaper way) gives the right ordering but I am stuck on how to get the XML(Elements) nesting as I have described.
;
WITH cte as
(
SELECT City,
FirstName,
LastName,
Amount,
SUM(Amount)OVER(PARTITION BY CONCAT(LastName, FirstName) ORDER BY CONCAT(LastName, FirstName) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Total
FROM Locations l
INNER JOIN Employees e
ON l.LocationID = e.LocationID
INNER JOIN Amounts a
ON a.EmpID = e.EmpID
GROUP BY City, LastName, FirstName, Amount
)
SELECT City, FirstName,LastName,Amount
FROM cte
ORDER BY SUM(Total)OVER(PARTITION BY City ORDER BY Total DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) DESC, Total DESC, Amount DESC
Here's a simpler looking query giving the same results:
SELECT City,
FirstName,
LastName,
Amount
FROM Locations l
INNER JOIN Employees e
ON l.LocationID = e.LocationID
INNER JOIN Amounts a
ON a.EmpID = e.EmpID
GROUP BY City, LastName, FirstName, Amount
order by city,SUM(Amount)OVER(PARTITION BY CONCAT(LastName, FirstName) ORDER BY CONCAT(LastName, FirstName) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) desc,Amount desc, firstname, lastname
SQLFiddle