How to pass sub Select * to Join by fluent nhibernate - sql

Can NH already create JOIN SELECT queries like
Select * From table1 Join ( Select * From table2 ...?
And why he can`t.
In my task I have a simple table:
TABLE [Message](
[Id] [int] NOT NULL,
[DocId] [int] NOT NULL,
[RecipientId] [bigint] NOT NULL,
[Text] [nvarchar](255) NULL,
[ArrivalDate] [date] NOT NULL
And Select:
SELECT msg.*
FROM [Message] msg
JOIN( SELECT msgMaxForDoc.DocId, MAX(msgMaxForDoc.ArrivalDate) AS ArrivalDate
FROM [Message] msgMaxForDoc
GROUP BY msgMaxForDoc.DocId) docAndDate ON msg.DocId = docAndDate.DocId AND msg.ArrivalDate = docAndDate.ArrivalDate
WHERE msg.RecipientId = #UserId
Full task sounds like: "Select all messages for user. If document (represented by DocId) contain more than one message, then get latest message". In result I must select one latest message for all DocId restricted by UserId
I found a similar question here:
nhibernate queryover join with subquery to get aggregate column
but it doesn't help (I don't have any links to another tablet DocId is just number).
And here Join a Subquery with NHibernate (but I can't separate select by two parts...)

As far as I know, it's impossible to join on a sub query using NHibernate. However, you could probably achieve your goal using the "WHERE EXISTS" method:
var dCriteria = DetachedCriteria.For<Message>("grouped")
.SetProjection(Projections.GroupProperty("grouped.DocId"))
.SetProjection(Projections.Max("grouped.ArrivalDate"))
.Add(Restrictions.EqProperty("msg.DocId", "grouped.DocId"))
.Add(Restrictions.EqProperty("msg.ArrivalDate", "grouped.ArrivalDate"))
.Add(Restrictions.Eq("grouped.RecipientId", #UserId));
var userMessages = Session.CreateCriteria<Message>("msg")
.Add(Subqueries.Exists(dCriteria)).List<Message>();

Related

Problem with creating a SQL view with multiple joins

I am trying to create a view which will be a base for Excel export in API. Basically, what it contains is information about particular projects. To said projects calculations can be added (it all happens on the form on frontend). Those calculations are called EBIT, EBIT+ and OVI. User can add either one, two or all of them, so for example there will be projects with only EBIT, but also projects with only for example EBIT, but also only with EBIT+ and OVI. View needs to return the project information with all chosen calculations in one row, so because some type of calculations wont be chosen by user there needs to be a type safety as well.
Code of my view:
CREATE VIEW [Signoff].[ExecelReport_uvw]
AS SELECT
project.ProjectName,
project.CreatedOn,
project.ProjectId,
subCategory.SubCategoryName,
projectStatus.StatusName,
overallCategory.CategoryName,
projectUserResponsible.UserName,
valueImprovementType.ValueImprovementTypeName,
OVI.OverallImprovementTypeName,
project.NameOfSuplier,
improvementCalculation.Baseline,
improvementCalculation.ImpactValue,
project.ContractStartDate,
project.ContractEndDate,
userbusinessController.UserName as businessControllerName,
businessControllerStatus.ApprovalStatusName businessControllerStatus,
userbusinesOwner.UserName as businessOwnerName,
businesOwnerStatus.ApprovalStatusName as businessOwnerStatus,
userbusinessCFO.UserName as businessCFOName,
businessCFOStatus.ApprovalStatusName as businessCFOStatus,
project.IsEbitda,
improvementCalculation.EBITDA
FROM [Signoff].[Project] as project
LEFT JOIN [Signoff].[OverallImprovementType] as OVI on project.OverallImprovementTypeId = OVI.OverallImprovementTypeId
LEFT JOIN [Signoff].[SubCategory] as subCategory on project.GPSubCategory = subCategory.SubCategoryId
LEFT JOIN [Signoff].[Category] as overallCategory on project.GPCategory = overallCategory.CategoryId
LEFT JOIN [Signoff].[ValueImprovementType] as valueImprovementType on project.ValueImprovementTypeId = valueImprovementType.ValueImprovementTypeId
LEFT JOIN [Signoff].[Status] as projectStatus on project.ProjectStatus = projectStatus.StatusId
LEFT JOIN [Signoff].[User] as projectUserResponsible on project.ProjectResponsible = projectUserResponsible.UserId
LEFT JOIN [Signoff].[ProjectUser] as projectUserbusinessControler on project.ProjectId = projectUserbusinessControler.ProjectId AND projectUserbusinessControler.ProjectRoleId = 'A36FC6CD-9ED7-4AA8-B1BE-355E48BDE25A'
LEFT JOIN [Signoff].[User] as userbusinessController on projectUserbusinessControler.ApproverId = userbusinessController.UserId
LEFT JOIN [Signoff].[ApprovalStatus] as businessControllerStatus on projectUserbusinessControler.ApprovalStatusId = businessControllerStatus.ApprovalStatusId
LEFT JOIN [Signoff].[ProjectUser] as projectUserbusinessOwner on project.ProjectId = projectUserbusinessOwner.ProjectId AND projectUserbusinessOwner.ProjectRoleId = 'E1E23E4F-1CA4-4869-9387-43CEDAEBBBB0'
LEFT JOIN [Signoff].[User] as userbusinesOwner on projectUserbusinessOwner.ApproverId = userbusinesOwner.UserId
LEFT JOIN [Signoff].[ApprovalStatus] as businesOwnerStatus on projectUserbusinessOwner.ApprovalStatusId = businesOwnerStatus.ApprovalStatusId
LEFT JOIN [Signoff].[ProjectUser] as projectUserbusinessCFO on project.ProjectId = projectUserbusinessCFO.ProjectId AND projectUserbusinessCFO.ProjectRoleId = 'DA17CF66-1D61-460E-BF87-5D86744DF22A'
LEFT JOIN [Signoff].[User] as userbusinessCFO on projectUserbusinessCFO.ApproverId = userbusinessCFO.UserId
LEFT JOIN [Signoff].[ApprovalStatus] as businessCFOStatus on projectUserbusinessCFO.ApprovalStatusId = businessCFOStatus.ApprovalStatusId
LEFT JOIN [Signoff].[ProjectImprovementCalculation] as projectImprovementCalculation on project.ProjectId = projectImprovementCalculation.ProjectId
LEFT JOIN [Signoff].[ImprovementCalculation] as improvementCalculation on projectImprovementCalculation.ImprovementCalculationId = improvementCalculation.ImprovementCalculationId
Improvement calculation table:
CREATE TABLE [Signoff].[ImprovementCalculation]
(
[ImprovementCalculationId] INT NOT NULL IDENTITY,
[Baseline] INT NOT NULL,
[TotalSpend] INT NOT NULL,
[ImpactValue] INT NOT NULL,
[ImpactPercentage] INT NOT NULL,
[EBITDA] INT NOT NULL,
[CalculationType] VARCHAR (255) NOT NULL
)
GO
ALTER TABLE [Signoff].[ImprovementCalculation]
ADD CONSTRAINT [PK_ImprovemntCalculation] PRIMARY KEY([ImprovementCalculationId]);
GO
Project Improvement Calculation table:
CREATE TABLE [Signoff].[ProjectImprovementCalculation]
(
[ProjectImprovementCalculationId] INT NOT NULL IDENTITY,
[ProjectId] UNIQUEIDENTIFIER NOT NULL,
[ImprovementCalculationId] INT NOT NULL,
)
GO
ALTER TABLE [Signoff].[ProjectImprovementCalculation]
ADD CONSTRAINT [PK_ProjectImprovementCalculation] PRIMARY KEY([ProjectImprovementCalculationId]);
GO
ALTER TABLE [Signoff].[ProjectImprovementCalculation]
ADD CONSTRAINT FK_ProjectProjectImprovementCalculation
FOREIGN KEY (ProjectId) REFERENCES [Signoff].[Project](ProjectId);
GO
ALTER TABLE [Signoff].[ProjectImprovementCalculation]
ADD CONSTRAINT FK_ImprovementCalculationProjectImprovementCalculation
FOREIGN KEY (ImprovementCalculationId) REFERENCES [Signoff].[ImprovementCalculation](ImprovementCalculationId);
GO
Just in case, although I don't think it's needed, the project table:
CREATE TABLE [Signoff].[Project]
(
[ProjectId] UNIQUEIDENTIFIER NOT NULL DEFAULT (NEWID()),
[ProjectName] NVARCHAR(50) NOT NULL,
[LegalEntity] UNIQUEIDENTIFIER NOT NULL,
[ValueImprovementTypeId] INT NOT NULL,
[OverallImprovementTypeId] INT NOT NULL,
[NameOfSuplier] NVARCHAR(50) NOT NULL,
[ContractStartDate] DATE NOT NULL,
[ContractEndDate] DATE NOT NULL,
[GPCategory] UNIQUEIDENTIFIER NOT NULL,
[GPSubCategory] UNIQUEIDENTIFIER NOT NULL,
[ProjectResponsible] UNIQUEIDENTIFIER NOT NULL,
[ProjectNumber] INT,
[FullProjectNumber] VARCHAR(55),
[ProjectStatus] UNIQUEIDENTIFIER NOT NULL DEFAULT '05c2f392-8b69-4915-a166-c4418889f9e8',
[IsCanceled] BIT NULL DEFAULT 0,
[IsEbitda] BIT NOT NULL DEFAULT 0,
[CreatedOn] DATETIME NOT NULL DEFAULT SYSDATETIME()
)
GO
ALTER TABLE [Signoff].[Project]
ADD CONSTRAINT [PK_Project] PRIMARY KEY([ProjectId]);
GO
ALTER TABLE [Signoff].[Project]
ADD CONSTRAINT [FK_ProjectStatus] FOREIGN KEY ([ProjectStatus]) REFERENCES [Signoff].[Status]([StatusId]);
GO
I have so came up with this solution, but it returns every single calculation in a different row in a table, and I want all calculations be in a single row with a project, so not what I am looking for:
LEFT JOIN [Signoff].[ProjectImprovementCalculation] as projectImprovementCalculation on project.ProjectId = projectImprovementCalculation.ProjectId
LEFT JOIN [Signoff].[ImprovementCalculation] as improvementCalculation on projectImprovementCalculation.ImprovementCalculationId = improvementCalculation.ImprovementCalculationId
Does anyone knows how to do it? Or I am approaching a problem from completely wrong way? If the information I have written is a bit chaotic, something isn't understandable, I can rephrase it.
I will assume that the available CalculationType values is fixed, that each project will have at most one improvement calculation per type, and that you wish to define fixed dedicated columns for BaseLine and ImpactValue each calculation type.
One approach is to use nested joins that will effectively LEFT JOIN the INNER JOINed combination of ProjectImprovementCalculation and ImprovementCalculation once for each calculation type. The results of each can then be referenced individually in the final select list.
Something like:
SELECT ...
IC_AAA.BaseLine, IC_AAA.ImpactValue,
IC_BBB.BaseLine, IC_BBB.ImpactValue,
...
FROM ...
LEFT JOIN Signoff.ProjectImprovementCalculation as PIC_AAA
JOIN Signoff.ImprovementCalculation as IC_AAA
ON IC_AAA.ImprovementCalculationId = PIC_AAA.ImprovementCalculationId
AND IC_AAA.CalculationType = 'AAA'
ON PIC_AAA.ProjectId = project.ProjectId
LEFT JOIN Signoff.ProjectImprovementCalculation as PIC_BBB
JOIN Signoff.ImprovementCalculation as IC_BBB
ON IC_BBB.ImprovementCalculationId = PIC_BBB.ImprovementCalculationId
AND IC_BBB.CalculationType = 'BBB'
ON PIC_BBB.ProjectId = project.ProjectId
...
The syntax is somewhat odd with two JOINs followed by two ON clauses. It would be clearer if parentheses were allowed, but (as far as I know) that is not part of the syntax.
There are several alternatives that accomplish the same. The following uses an OUTER APPLY:
SELECT ...
AAA.BaseLine, AAA.ImpactValue,
BBB.BaseLine, BBB.ImpactValue,
...
FROM ...
OUTER APPLY (
SELECT IC.*
FROM Signoff.ProjectImprovementCalculation as PIC
JOIN Signoff.ImprovementCalculation as IC
ON IC.ImprovementCalculationId = PIC.ImprovementCalculationId
AND IC.CalculationType = 'AAA'
WHERE PIC.ProjectId = project.ProjectId
) AAA
OUTER APPLY (
SELECT IC.*
FROM Signoff.ProjectImprovementCalculation as PIC
JOIN Signoff.ImprovementCalculation as IC
ON IC.ImprovementCalculationId = PIC.ImprovementCalculationId
AND IC.CalculationType = 'BBB'
WHERE PIC.ProjectId = project.ProjectId
) BBB
...
Using a Common Table Expression (CTE) can reduce some of the duplication as well as making the query a bit more readable.
;WITH ImprovementCTE AS (
SELECT PIC.ProjectId, IC.*
FROM Signoff.ProjectImprovementCalculation as PIC
JOIN Signoff.ImprovementCalculation as IC
ON IC.ImprovementCalculationId = PIC.ImprovementCalculationId
)
SELECT ...
AAA.BaseLine, AAA.ImpactValue,
BBB.BaseLine, BBB.ImpactValue,
...
FROM ...
LEFT JOIN ImprovementCTE AAA
ON AAA.ProjectId = project.ProjectId
AND AAA.CalculationType = 'AAA'
LEFT JOIN ImprovementCTE BBB
ON BBB.ProjectId = project.ProjectId
AND BBB.CalculationType = 'BBB'
...
You might also try using conditional aggregation in a single CROSS APPLY:
SELECT ...
IC.BaseLineAAA, IC.ImpactValueAAA,
IC.BaseLineBBB, IC.ImpactValueBBB,
...
FROM ...
CROSS APPLY (
SELECT
BaseLineAAA = SUM(CASE WHEN IC.CalculationType = 'AAA' THEN IC.BaseLine),
ImpactValueAAA = SUM(CASE WHEN IC.CalculationType = 'AAA' THEN IC.ImpactValue),
BaseLineBBB = SUM(CASE WHEN IC.CalculationType = 'BBB' THEN IC.BaseLine),
ImpactValueBBB = SUM(CASE WHEN IC.CalculationType = 'BBB' THEN IC.ImpactValue),
...
FROM Signoff.ProjectImprovementCalculation as PIC
JOIN Signoff.ImprovementCalculation as IC
ON IC.ImprovementCalculationId = PIC.ImprovementCalculationId
WHERE PIC.ProjectId = project.ProjectId
) IC
I expect there are additional approaches such as using a PIVOTs.
If the above appears to suit your needs, you should still run tests and examine the execution plans to see which performs best. Some may have a tendency to retrieve all ImprovementCalculation rows even when a subset of projects are selected.
To handle missing calculation types, you can use the ISNULL() function to provide a default. If you need to force a blank value in an otherwise numeric result, you might need to use something like ISNULL(CONVERT(VARCHAR(50), result), '').

Sql query: How to use right outer join correctly

I have to tables: Register and Price.
CREATE TABLE [dbo].[Register](
[RegisterID] [int] NULL,
[GroupID] [int] NULL,
[TestID] [int] NULL)
CREATE TABLE [dbo].[Price](
[ID] [int] NULL,
[GroupID] [int] NULL,
[Price] [bigint] NULL,
[Status] [bit] NULL)
Assuming information similar to the image above, consider the following query
SELECT Price.*
FROM Register RIGHT OUTER JOIN Price ON Register.GroupID = Price.GroupID
WHERE (Price.Status = 1) AND (Register.TestID = 50)
The output will be displayed as shown below.
My expectation is that the first and second rows of the price table will be displayed. So where is my mistake and how should I change the query to get the right output?
The restriction on the Register table which currently appears in the WHERE clause would have to be moved to the ON clause to get the behavior you want:
SELECT p.*
FROM Register r
RIGHT JOIN Price p ON r.GroupID = p.GroupID AND r.TestID = 50;
WHERE p.Status = 1
Note that more typically you would express the above via a left join:
SELECT p.*
FROM Price p
LEFT JOIN Register r ON r.GroupID = p.GroupID AND r.TestID = 50
WHERE p.Status = 1;

Improve performance of multiple insert and update queries

I have two tables: Sale_Source (10.000 rows) and Sale_Target (1 billion rows). I have four queries to INSERT and UPDATE Sale_Target with the data from Sale_Source.
Source table has a nonclustered index on id and Date
Target table has indexes on id and Date but i have disable them to improve the Performance
Queries
INSERT query #1:
INSERT INTO dbo.Sale_Target (id, Salevalue, Salestring, Date)
SELECT id, Salevalue, Salestring, Date
FROM dbo.Sale_Source s
WHERE NOT EXISTS (SELECT 1 FROM dbo.Sale_Target t ON s.id = t.id)
UPDATE query #1 (when the date are the same):
UPDATE t
SET t.Salevalue = s.Salevalue,
t.Salestring = s.Salestring
FROM dbo.Sale_Source s
JOIN dbo.Sale_Target t ON t.id = s.id AND s.Date = t.Date
WHERE t.Salevalue <> s.Salevalue OR t.Salestring <> s.Salestring
UPDATE query #2 (when date on SaleSource > Date on SaleTarget):
UPDATE t
SET t.Salevalue = s.Salevalue,
t.Salestring = s.Salestring
FROM dbo.Sale_Source s
JOIN dbo.Sale_Target t ON t.id = s.id AND s.Date > t.Date
UPDATE query #3 (when id on Source is null in join clause):
UPDATE t
SET t.Salevalue = null,
t.Salestring = null
FROM dbo.Sale_Source s
LEFT JOIN dbo.Sale_Target t ON t.id = s.id
WHERE s.id IS NULL
The four queries require 1 hr. 30 mins. to finish, which is very slow for the source table with only 10.000 rows. I think the problem here is that each time when four queries run, they need to JOIN two source and target tables again, which cost a lot of time.
Therefore I have an idea:
I will create a query which saves the matched rows between two tables (source and target) into table temp_matched, and save the non matched rows (non matched to target) into temp_nonmatched.
For this, I have problem now with MERGE query because in MERGE we can't save data into another table.
Use temp_nonmatched in INSERT query, in UPDATE query. I will replace table Sale_Source with temp_matched.
Do you have any idea to do it or can we optimize these four queries in another way?
Thank you.
Table Definition:
CREATE TABLE [dbo].[Sale_Target](
[id] [numeric](8, 0) NOT NULL,
[Salevalue] [numeric](8, 0) NULL,
[Salestring] [varchar](20) NULL,
[Date] [Datetime2](7) NULL
) ON [PRIMARY]
CREATE NONCLUSTERED COLUMNSTORE INDEX "NCCI_Sale_Target"
ON [dbo].[Sale_Target] (id,Date)
CREATE TABLE [dbo].[Sale_Source](
[id] [numeric](8, 0) NOT NULL,
[Salevalue] [numeric](8, 0) NULL,
[Salestring] [varchar](20) NULL,
[Date] [Datetime2](7) NULL
) ON [PRIMARY]
CREATE NONCLUSTERED COLUMNSTORE INDEX "NCCI_Sale_Source"
ON [dbo].[Sale_Target] (id,Date)
Target Tables has no Index.
First thing I would do is index the Target Table on id and date.

Match records with specific ids

Let say I have this query to match an employee with the specific orders 1982 and 138923, is there another way without using having and group by clause ?
I want to sort out the employees that have two or more specific order ids.
SELECT [EmployeeId], COUNT([OrderId])
FROM [dbo].[EmployeeOrderRelation]
WHERE [OrderId] IN (1982, 138923)
GROUP BY [EmployeeId]
HAVING count([OrderId]) > 1;
You can use EXISTS :
SELECT er.*
FROM [dbo].[EmployeeOrderRelation] er
WHERE [OrderId] IN (1982, 138923) AND
EXISTS (SELECT 1
FROM [dbo].[EmployeeOrderRelation] er1
WHERE er1.[EmployeeId] = er.[EmployeeId] AND er1.[OrderId] <> er.[OrderId]
);
I don't know why you need different version of the query instead of aggregation query, if the performance concern then this version some time helpful in performance if you have a right index on (EmployeeId, OrderId).
You can use correlated subquery
SELECT a.*
FROM [dbo].[EmployeeOrderRelation] a
WHERE [OrderId] IN (1982, 138923) and exists (select 1 from [dbo].[EmployeeOrderRelation] b where a.[EmployeeId]=b.[EmployeeId] and b.[OrderId] IN (1982, 138923) having count(orderid)>=2)
Use INTERSECT to return EmployeeId having both values:
SELECT [EmployeeId] FROM [dbo].[EmployeeOrderRelation] WHERE [OrderId] = 1982
INTERSECT
SELECT [EmployeeId] FROM [dbo].[EmployeeOrderRelation] WHERE [OrderId] = 138923
Or do a self join:
select t1.[EmployeeId]
from [dbo].[EmployeeOrderRelation] t1
join [dbo].[EmployeeOrderRelation] t2
on t1.[EmployeeId] = t2.[EmployeeId]
WHERE t1.[OrderId] = 1982
AND t2.[OrderId] = 138923

How can I efficiently query and index a view that involves a full outer join?

We have a data processing application that has two separate paths that should eventually produce similar results. We also have a database-backed monitoring service that compares and utilizes the results of this processing. At any point in time, either of the two paths may or may not have produced results for the operation, but I want to be able to query a view that tells me about any results that have been produced.
Here's a simplified example of the schema I started with:
create table LeftResult (
DateId int not null,
EntityId int not null,
ProcessingValue int not null
primary key ( DateId, EntityId ) )
go
create table RightResult (
DateId int not null,
EntityId int not null,
ProcessingValue int not null
primary key ( DateId, EntityId ) )
go
create view CombinedResults
as
select
DateId = isnull( l.DateId, r.DateId ),
EntityId = isnull( l.EntityId, r.EntityId ),
LeftValue = l.ProcessingValue,
RightValue = r.ProcessingValue,
MaxValue = case
when isnull( l.ProcessingValue, 0 ) > isnull( r.ProcessingValue, 0 )
then isnull( l.ProcessingValue, 0 )
else isnull( r.ProcessingValue, 0 )
end
from LeftResult l
full outer join RightResult r
on l.DateId = r.DateId
and l.EntityId = r.EntityId
go
The problem with this is that Sql Server always chooses to scan the PK on LeftResult and RightResult rather than seek, even when queries to the view include DateId and EntityId as predicates. This seems to be due to the isnull() checks on the results. (I've even tried using index hints and forceseek, but without avail -- the query plan still shows a scan.)
However, I can't simply replace the isnull() results, since either the left or right side could be missing from the join (because the associated process hasn't populated the table yet).
I don't particularly want to duplicate the MaxValue logic across all of the consumers of the view (in reality, it's quite a bit more complex calculation, but the same idea applies.)
Is there a good strategy I can use to structure this view or queries against it so that the
query plan will utilize a seek rather than a scan?
try using left outer join for one of the tables, then union those results with the excluded rows from the other table.
like:
select (...)
from LeftResult l
left outer join RightResult r
on l.DateId = r.DateId
and l.EntityId = r.EntityId
(...)
UNION ALL
select (...)
from RightResult r
leftouter join LeftResult l
on l.DateId = r.DateId
and l.EntityId = r.EntityId
WHERE
l.dateid is null