How to show only one row - sql-server

I have this table structure and the sample data as well. I want to get only one row of the data. But instead it is giving me rows equal to it's child records.
--DROP TABLE [Detail];
--DROP TABLE [Master];
--CREATE TABLE [Master]
--(
--ID INT NOT NULL PRIMARY KEY,
--Code VARCHAR(25)
--);
--INSERT INTO [Master] VALUES (1, 'CASH');
--INSERT INTO [Master] VALUES (2, 'CASH');
--CREATE TABLE [Detail]
--(
--ID INT NOT NULL PRIMARY KEY,
--MasterID INT,
--DrAmount Numeric,
--CrAmount Numeric,
--CONSTRAINT FK_MASTER FOREIGN KEY (MasterID)
--REFERENCES [Master](ID)
--);
--INSERT INTO [Detail] VALUES (1, 1, '2200', NULL);
--INSERT INTO [Detail] VALUES (2, 1, NULL, '3200');
--INSERT INTO [Detail] VALUES (3, 1, '1000', NULL);
--INSERT INTO [Detail] VALUES (4, 2, NULL, '3200');
--INSERT INTO [Detail] VALUES (5, 2, '3200', NULL);
Here is the query and result:
SELECT [MASTER].[Code], [DETAIL].[MasterID], [DETAIL].[CrAmount]
FROM [MASTER], [DETAIL]
WHERE [MASTER].[ID] = [DETAIL].[MasterID]

Looks like you need GROUP BY and as #HoneyBadger suggests, it would be better to use the modern explicit join syntax - it is much more clear:
select m.code, d.masterid, sum(d.cramount) amount
from [master] m
join[detail] d on m.[id] = d.[masterid]
group by m.code, d.masterid
Result:
code masterid amount
CASH 1 3200
CASH 2 3200

Related

SQL filter results based on user/role combination

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.

TRIGGER AFTER INSERT SELECT MIN(COUNT) insert ID

I'm trying to create a trigger after an insert on the eventss table. The trigger should select the Bcoordinator_ID from the bookingCoordinator table where they have the minimum number of occurrences in the eventss table.
Here's my table data followed by the trigger. It doesn't like the minCount in the values, I think it's looking for and int.
DROP TABLE eventsBooking
CREATE TABLE eventsBooking
(
EBK INT NOT NULL IDENTITY(100, 1),
booking_ID AS 'EBK'+CAST( ebk as varchar(10)) PERSISTED PRIMARY KEY,
bookingDate DATE,
Bcoordinator_ID VARCHAR (20),
eventss_ID VARCHAR (20) NOT NULL
)
INSERT INTO eventsBooking
VALUES ('2015-01-07 11:23:00', NULL, 'EVT100');
Eventss table:
EVT INT NOT NULL IDENTITY(100, 1),
eventss_ID AS 'EVT' + CAST(evt as varchar(10)) PERSISTED PRIMARY KEY,
eventsName varchar(50),
noOfStages SMALLINT,
noOfRounds SMALLINT,
eventsDate DATE,
entryFee DECIMAL (7,2),
venue_ID VARCHAR (20) NOT NULL,
judges_ID VARCHAR (20)
INSERT INTO eventss
VALUES ('Swimming Gala 2015', '3', '7', '2015-01-07 09:00:00', '35.00', 'VEN101', 'JUD100');
CREATE TABLE bookingCoordinator
(
BCO INT NOT NULL IDENTITY(100, 1),
Bcoordinator_ID AS 'BCO'+CAST( bco as varchar(10)) PERSISTED PRIMARY KEY,
forename varchar(20) NOT NULL,
familyName varchar(50)
)
INSERT INTO bookingCoordinator VALUES ('Steve', 'Wills');
Trigger:
CREATE TRIGGER TRGinsertJudge
ON [dbo].[eventss]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.eventsBooking (Bcoordinator_ID, bookingDate, Eventss_ID)
VALUES(minCount, getdate(), 100)
SELECT MIN(COUNT(Bcoordinator_ID)) AS minCount
FROM eventsBooking
END
You can't do an aggregation of an aggregation i.e. MIN(COUNT(1))
If you just want the Bcoordinatior_ID with the least counts in eventsBooking, do this
select top 1 bcoordinator_id
from eventsBooking
group by bcoordinator_id
order by count(1) asc
And you don't use VALUES() in an INSERT INTO ... SELECT statement
Also, in your current code, since eventsBooking.bcoordinator_id is always null, you need to join to the actual table of bookingCoordinators to return booking coordinators without any events booked.
So your complete trigger statement should be
INSERT INTO dbo.eventsBooking (Bcoordinator_ID, bookingDate, Eventss_ID)
select
top 1
bookingcoordinator.bcoordinator_id, getdate(), 100
from bookingCoordinator left join eventsBooking
on bookingCoordinator.Bcoordinator_ID = eventsBooking.Bcoordinator_ID
group by bookingcoordinator.bcoordinator_id
order by count(1) asc

how can i perfom computed columns in Oracle like in SQL Server

How can I create the table like this with compound columns in oracle:
The table definition in SQL Server is:
CREATE TABLE [dbo].[CCtestUsingSubstring] (
[EmpNumb] INT NOT NULL,
[Designation] VARCHAR(50) NOT NULL,
[DOBirth] DATETIME NOT NULL,
[DORetirement] AS REPLACE([Designation],'E','O') )
insert into [CCtestUsingSubstring] values(1,'Developer',1/10/1992)
Select * from [CCtestUsingSubstring]
Oracle calls this "virtual column":
CREATE TABLE CCtestUsingSubstring (
EmpNumb INT NOT NULL,
Designation VARCHAR2(50) NOT NULL,
DOBirth DATE NOT NULL,
DORetirement AS (REPLACE(Designation,'e','O')));
insert into CCtestUsingSubstring (EmpNumb, Designation, DOBirth)
values(1,'Developer',date '1992-10-01');
Select * from CCtestUsingSubstring
CREATE OR REPLACE TYPE my_tab_t AS TABLE OF VARCHAR2(30);
/
CREATE TABLE nested_table (id NUMBER, col1 my_tab_t)
NESTED TABLE col1 STORE AS col1_tab;
INSERT INTO nested_table VALUES (1, my_tab_t('A'));
INSERT INTO nested_table VALUES (2, my_tab_t('B', 'C'));
INSERT INTO nested_table VALUES (3, my_tab_t('D', 'E', 'F'));
COMMIT;
CREATE TABLE [dbo].[CCtestUsingSubstring] (
[EmpNumb] INT NOT NULL,
[Designation] VARCHAR(50) NOT NULL,
[DOBirth] DATETIME NOT NULL,
[DORetirement] AS REPLACE([Designation],'E','O') )
insert into [CCtestUsingSubstring] values(1,'Developer',1/10/1992)
Select * from [CCtestUsingSubstring]

TSQL - Bringing Data Together from Different Sources ...refactoring PK and FKs

I have various offices and one central head office. Each office has its own SQL Server 2008 instance so each office has its own data set with its own set of IDs.
Each office has already imported data into the head office and stored the data on a set of STAGING_Tables that look like this.
DECLARE #STAGING_COUNTRY TABLE
(
Original_CountryID INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
Data VARCHAR(200) NOT NULL
);
DECLARE #STAGING_CITY TABLE
(
Original_CityID INT NOT NULL,
Original_CountryID_FK INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
OtherData VARCHAR(100) NOT NULL
);
STAGING_COUNTRY has the original ID of each row (which off course will be duplicated since each office will have ID=1 for the 1st row on their Country table) and also has a unique OfficeID value that together with the Original_CountryID ..makes a unique value.
STAGING_CITY has also the original ID of each row, the unique OfficeID value that represent each office and in this case a FK to CountryID, (but of course at this point we have a reference to the Original_CountryID ..that in conjunction with the office ID could be identified).
Let's add some dummy rows:
/* ADD DUMMY VALUES TO STAGING_COUNTRY */
INSERT INTO #STAGING_COUNTRY
(Original_CountryID, OfficeID, Data) VALUES (1, 'Office1', 'USA')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (2, 'Office1', 'Canada')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (3, 'Office1', 'Japan')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (1, 'Office2', 'USA')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (1, 'Office2', 'Italy')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (3, 'Office2', 'Canada')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (3, 'Office3', 'Canada')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (2, 'Office3', 'France')
INSERT INTO #STAGING_COUNTRY (Original_CountryID, OfficeID, Data)
VALUES (3, 'Office3', 'USA')
/* ADD DUMMY VALUES TO STAGING_CITY */
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK, OfficeID, OtherData) VALUES
(1, 1, 'Office1', 'New York')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (2, 1, 'Office1', 'Vancouver')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (3, 1, 'Office1', 'Tokia')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (1, 2, 'Office2', 'New York')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (2, 2, 'Office2', 'Rome')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (3, 2, 'Office2', 'Vancouver')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (1, 3, 'Office3', 'Vancouver')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (2, 3, 'Office3', 'Paris')
INSERT INTO #STAGING_CITY (Original_CityID, Original_CountryID_FK,
OfficeID, OtherData) VALUES (3, 3, 'Office3', 'New York')
The central head office wants to run reports from a central dtabase that pretty much contains copy all the data from all offices but in order to make this reporting DB optimized, we need to reshuffle a bit the STAGING_Tables ...and reorganize the data in FINAL_Tables that look like this:
DECLARE #FINAL_COUNTRY TABLE
(
CountryID INT IDENTITY PRIMARY KEY,
Original_CountryID INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
Data VARCHAR(200) NOT NULL
);
DECLARE #FINAL_CITY TABLE
(
CityID INT IDENTITY PRIMARY KEY,
Original_CityID INT NOT NULL,
CountryID_FK INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
OtherData VARCHAR(100) NOT NULL
);
PROBLEM:
The FINAL_COUNTRY and FINAL_CITY tables should be as optimized as possible for reporting purposes. These reports will be written in T-SQL stored procedures.
QUESTION:
What is the best way to reorganize the FINAL_Tables so that each record has a TRUE PK identifier (like in the original Office_Tables) and each FK is updated to point to the right newly created PK ...at the server level?
NOTE:
Please note that both staging & final tables are inside the same DB, on the server.
Also we still need to keep the OriginalIDs on the FINAL_Tables for other purposes.
GOALS:
The main goal here is to reorganize into a set of tables that can be easily indexed for performance purposes.
Please ask more info if needed.
Many many thanks in advanced...
This is probably just a partial answer. You may want to consider putting a generic IDENTITY id on each of your staging tables. Something like:
DECLARE #STAGING_COUNTRY TABLE
(
Stage_Country_id INT IDENTITY(1,1) NOT NULL,
Original_CountryID INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
Data VARCHAR(200) NOT NULL
);
DECLARE #STAGING_CITY TABLE
(
Stage_City_id INT IDENTITY(1,1) NOT NULL,
Original_CityID INT NOT NULL,
Original_CountryID_FK INT NOT NULL,
OfficeID VARCHAR(10) NOT NULL,
OtherData VARCHAR(100) NOT NULL
);
Your final tables should not have the original_ids as you should only have 1 record per city / country in them.
Then I think you'd need some sort of cross reference tables to bridge your final tables to your stage tables. That would look like this:
DECLARE #COUNTRY_xref TABLE
(
country_xref_id INT IDENTITY(1,1) not null,
CountryID INT not null,
Stage_Country_id INT
);
DECLARE #CITY_xref TABLE
(
city_xref_id INT IDENTITY(1,1) not null,
CityID INT not null,
Stage_City_id INT not null
);
Are you also asking what the loading / conversion process would look like or was this more about the schema?
your final tables would probably look like this:
DECLARE #FINAL_COUNTRY TABLE
(
CountryID INT IDENTITY PRIMARY KEY,
Data VARCHAR(200) NOT NULL
);
DECLARE #FINAL_CITY TABLE
(
CityID INT IDENTITY PRIMARY KEY,
CountryID_FK INT NOT NULL,
OtherData VARCHAR(100) NOT NULL
);

Persisted computed column with subquery

I have something like this
create function Answers_Index(#id int, #questionID int)
returns int
as begin
return (select count([ID]) from [Answers] where [ID] < #id and [ID_Question] = #questionID)
end
go
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
[Index] as [dbo].[Answers_Index]([ID], [ID_Question]),
)
go
insert into Answers ([ID_Question], [Text]) values
(1, '1: first'),
(2, '2: first'),
(1, '1: second'),
(2, '2: second'),
(2, '2: third')
select * from [Answers]
Which works great, however it tends to slow down queries quite a bit. How can I make column Index persisted? I have tried following:
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
)
go
create function Answers_Index(#id int, #questionID int)
returns int
with schemabinding
as begin
return (select count([ID]) from [dbo].[Answers] where [ID] < #id and [ID_Question] = #questionID)
end
go
alter table Answers add [Index] as [dbo].[Answers_Index]([ID], [ID_Question]) persisted
go
insert into Answers ([ID_Question], [Text]) values
(1, '1: first'),
(2, '2: first'),
(1, '1: second'),
(2, '2: second'),
(2, '2: third')
select * from [Answers]
But that throws following error: Computed column 'Index' in table 'Answers' cannot be persisted because the column does user or system data access. Or should I just forget about it and use [Index] int not null default(0) and fill it in on insert trigger?
edit: thank you, final solution:
create trigger [TRG_Answers_Insert]
on [Answers]
for insert, update
as
update [Answers] set [Index] = (select count([ID]) from [Answers] where [ID] < a.[ID] and [ID_Question] = a.[ID_Question])
from [Answers] a
inner join [inserted] i on a.ID = i.ID
go
You could change the column to be a normal column and then update its value when you INSERT/UPDATE that row using a trigger.
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
[Index] Int null
)
CREATE TRIGGER trgAnswersIU
ON Answers
FOR INSERT,UPDATE
AS
DECLARE #id int
DECLARE #questionID int
SELECT #id = inserted.ID, #questionID = inserted.ID_question
UPDATE Answer a
SET Index = (select count([ID]) from [Answers] where [ID] < #id and [ID_Question] = #questionID)
WHERE a.ID = #id AND a.ID_question = #questionID
GO
NB* This is not fully correct as it wont work correctly on UPDATE as we wont have the "inserted" table to reference to get the ID and questionid. There is a way around this but i cant remember it right now :(
Checkout this for more info
Computed columns only store the formula of the calculation to perform. That is why it will be slower when querying the computed column from the table. If you want to persist the values to an actual table column, then you are correct about using a trigger.

Resources