Three-way join using one SQL table - c#

Suppose there is a MSSQL table, UserPost, that represents something user has posted, with these fields:
ID | dateAdded | parentPostID | postBody
A user in the system could create a Request, receive a Response, and then other users could Comment on the Response. Ie, Request <=1:many=> Reponse <=1:many=> Comment (think StackOverlow's Question > Answer > Comment model-like).
All user posts (Request, Response and Comment) are represented by UserPost rows, where Request has parentPostID = null;; Responses' parentPostID is the Request's ID, and Comment's parentPostID is the ID of the Response.
I need to output everything in a simple fashion:
Request 1
- Response A
-- Comment (i)
-- Comment (ii)
- Response B
-- Comment (i)
Request 2
...
Question: which SQL statement returns the needed information in the most usable way?
I'm struggling to write a three-way join between (UserPosts) as Requests [join] (UserPosts) as Responses [join] (UsersPosts) as Comments but am not sure this is the easiest way.
Bonus: is it possible to do this using C# Linq?

Can't think of a way to do this in LINQ. I've removed unused columns. Luckily this is a bounded hierarchy. I'm using the new hierarchyid data type, which has the desired sort order:
create table UserPosts (
ID int not null,
ParentID int null
)
go
insert into UserPosts (ID,ParentID)
select 1,null union all
select 2,null union all
select 3,1 union all
select 4,2 union all
select 5,3 union all
select 6,1 union all
select 7,6
go
select
*
from
UserPosts up
left join
UserPosts up_1st
on
up.ParentID = up_1st.ID
left join
UserPosts up_2nd
on
up_1st.ParentID = up_2nd.ID
order by
CONVERT(hierarchyid,
COALESCE('/' + CONVERT(varchar(10),up_2nd.ID),'') +
COALESCE('/' + CONVERT(varchar(10),up_1st.ID),'') +
'/' + CONVERT(varchar(10),up.ID) + '/'
)
HierarchyIDs (as strings) look like /GrandParent/Parent/Child/ - so we construct values that look like this. Obviously, if we don't have a grandparent (up_2nd.ID is null, since we can't achieve 2 left joins as described), then we just want to construct /Parent/Child/ - this is what the 1st COALESCE is helping us achieve. Similarly, if we can't find any parents (both up_1st.ID and up_2nd.ID are null), then both of the COALESCEs just turn into empty strings, and we end up construcing /ID/.
You can add:
CASE
WHEN up_2nd.ID is not null then 'Comment'
WHEN up_1st.ID is not null then 'Response'
ELSE 'Request'
END as Level
to your select list, if you want to track what level the item is (or use numerics instead, if desired)

Related

C# SQL Server to JSON with key groups

I've this table with is basically translations:
Key CultureId Txt
$HELLO en-GB Hello
$HELLO pt-BR Olá
$WELCOME en-GB Welcome
$WELCOME pt-BR Olá
And a select like:
Select Key, CultureId, Txt
From Xlations
Order by Key
This is an endpoint rest api, so I'd like a result like
{
"$HELLO":{
"en-GB":"Hello",
"pt-BR":"Olá"
},
"$WELCOME":{
"en-GB":"Bem Vindo",
"pt-BR":"Welcome"
}
}
So, keys with no arrays, totally in objects where the field key will be the parent of the assigned translations.
I know how to do it by creating few iterations on my code, but I was wondering if there is some shorthand for that because I don't want to keep my code huge and complex with iterates and nested iterates. Not sure if such things are possible, but: Anywone know some easy and simple way ?
JSON output is usually generated using the FOR JSON clause. In your case, the required JSON output has variable key names, so FOR JSON is probably not an option. But, if the SQL Server version is 2017 or higher, you may try to generate the JSON manually, using string concatenation and aggregation. Also, as #Charlieface commented, escape the generated text with STRING_ESCAPE().
Test table:
SELECT *
INTO Xlations
FROM (VALUES
(N'$HELLO', N'en-GB', N'Hello'),
(N'$HELLO', N'pt-BR', N'Olá'),
(N'$WELCOME', N'en-GB', N'Welcome'),
(N'$WELCOME', N'pt-BR', N'Bem Vindo')
) v ([Key], CultureId, Txt)
Statement:
SELECT CONCAT(
N'{',
STRING_AGG(CONCAT(N'"', STRING_ESCAPE([Key], 'json'), N'":', [Value]), N','),
N'}'
) AS Json
FROM (
SELECT DISTINCT x.[Key], a.[Value]
FROM Xlations x
OUTER APPLY (
SELECT CONCAT(
N'{',
STRING_AGG(CONCAT(N'"', STRING_ESCAPE(CultureId, 'json'), N'":"', STRING_ESCAPE(Txt, 'json'), N'"'), N','),
N'}'
) AS [Value]
FROM Xlations
WHERE [Key] = x.[Key]
) a
) t
Result:
{
"$HELLO":{"en-GB":"Hello","pt-BR":"Olá"},
"$WELCOME":{"en-GB":"Welcome","pt-BR":"Bem Vindo"}
}
You can not use your sql functions to do this and you have to do it manually.
SELECT CONCAT('{',string_agg(jsoncol,','),'}') Json
FROM
(SELECT '1' AS col, CONCAT('"',[key],'"',':{' + string_agg(jsoncol,',') ,'}') AS jsoncol
FROM
(SELECT [key],CONCAT('"',CultureId,'":"',txt ,'"') AS jsoncol FROM tb) t
GROUP BY [key]) t
GROUP BY col
demo in dbfiddle<>uk
The answer given by #Zhorov is good, but you can improve it by only querying the table once, aggregating then aggregating again.
This should be more performant than a correlated subquery.
SELECT CONCAT(
N'{',
STRING_AGG(CONCAT(N'"', STRING_ESCAPE([Key], 'json'), N'":', [Value]), N','),
N'}'
) AS Json
FROM (
SELECT x.[Key], CONCAT(
N'{',
STRING_AGG(CONCAT(N'"', STRING_ESCAPE(CultureId, 'json'), N'":"', STRING_ESCAPE(Txt, 'json'), N'"'), N','),
N'}'
) AS [Value]
FROM Xlations x
GROUP BY x.[Key]
) t
db<>fiddle

Subtract data between tables when X Element matches

The explanation is a little long but I want to make me understand with the problem as detailed as possible. I have 4 tables, 2 indicate the information of material used and material returned (Transactions and Returns) and the other 2 tablan detail what material was used or was returned (Trans_det1 and Devol_det1)
Each material that is used or not is detailed by boletas(tickets), which are detailed by work orders, an example:
I have the following sentences:
--REGISTRATION OF INCOME
SELECT*FROM Transa WHERE orden='GORE-999888'
--REGISTRATION OF RETURNS
SELECT*FROM Devol WHERE orden='GORE-999888'
It returns this:
The Transa table returns all the tickets that were used for that order and Devol also but shows me the boletas(tickets) that were used to return material.
MATERIALS DETAILS
--WHAT WAS USED
SELECT Transa_det1.boleta, Transa_det1.rollo, Transa_det1.cantidad FROM Transa_det1,Transa
WHERE Transa.boleta=Transa_det1.boleta AND orden='GORE-999888'
--WHAT WAS RETURNED
SELECT Devol_det1.boleta, Devol_det1.rollo, Devol_det1.cantidad FROM Devol_det1, Devol
WHERE Devol_det1.boleta=Devol.boleta AND orden='GORE-999888'
It returns this:
It returns the rollos that were used in the tickets from order XXXX and how much was used, the second table shows me the number of rollos that were returned.
My question, for the RT0102 rollo I am using 100.5 but then I returned 100 at the end what I really used was 0.5, same for RT0103 use 250 but then I returned 50, there is a way to do the subtraction if the rollo match while they are of the same order all in a single query? I mean I need a way to return this:
RT0103 0.5
RT0102 200
FH0091 465.75 //DON'T SUFFER CHANGES BECAUSE I DIDN'T RETURN THIS MATERIAL
You can use OUTER APPLY like following.
SELECT td.boleta,
td.rollo,
td.cantidad,
( td.cantidad - Isnull(c.ret, 0) ) AS Returned
FROM transa_det1 td
INNER JOIN transa ta
ON ta.boleta = td.boleta
OUTER apply (SELECT top 1 d1.cantidad AS Ret
FROM devol_det1 d1
INNER JOIN devol d
ON d1.boleta = d.boleta
WHERE d1.rollo = td.rollo and d.orden=ta.orden)c
where ta.orden = 'GORE-999888'
One suggestion, always use JOINS, this makes your query more readable and makes it look very clear as to which join corresponds to which condition.
Below would be the query to get the expected results, You can use case statement for
select t1.rollo,
case when t2.cantidad is null then 0 // if canditad does not have value in DevolDtl then return 0 else do the subtraction
else t1.cantidad-t2,canditad end as result from
(
SELECT Transa_det1.boleta, Transa_det1.rollo, Transa_det1.cantidad FROM Transa_det1,Transa
WHERE Transa.boleta=Transa_det1.boleta AND orden='GORE-999888') t1
left outer join
(
SELECT Devol_det1.boleta, Devol_det1.rollo, Devol_det1.cantidad FROM Devol_det1, Devol
WHERE Devol_det1.boleta=Devol.boleta AND orden='GORE-999888') t2
on t1.boleta=t2.boleta;

Create complex query to execute queries stored in column?

I am trying to get some information from a table, when a (one or more than one) condition(s) is(are) met but this conditions are stored in other table.
Here is a screenshot of the 3 tables:
So I need a query to do this:
Given a Status (IDStatus) and an Action (IDAction)
If there is an IDCondition (it can be NULL, one, or more than one)
A. If there is one, use the IDocField as a column from a table called IDoc, using ConditionOperator as the condition (read this as =, <, >) and the value on ConditionValue.
B. If there are more than one, the same as before, but use each condition with AND.
C. If it is NULL, directly go to point 3
Get Subject, SendTo, CC, CCO, and FileTemplate that matched all of the above explained.
I want to try this because it would reduce my C# code, but if this is too complex or not plausible, I will do multiple queries like what I explained above.
I am asking this, because I am not sure how to get multiple conditions in a SELECT. I mean by this:
If there is more than one condition, how can I execute them or get all of them in a single query but that has all the returning things on point 3 ?
Example:
SELECT *
FROM WF_MailCondition
WHERE IDCondition = 1
this will return all conditions (keys can repeat) that has IDCondition equal to 1. But then I have to use all the three columns to create a condition like ID = 1 been ID in IDocField, = in ConditionOperator and 1 in ConditionValue.
I know there is some inconsistencies in the image, like IDCondition is NN (not null) but I said that it can be null (I am fixing this things).
My SQL:
SELECT
Subject, SendTo, CC, CCO, FileTemplate
FROM
WF_Mail AS M
INNER JOIN
WF_MailStatusAction AS S ON S.IDMail = M.IDMail
WHERE
(SELECT "HERE SELECT THE 3 COLUMNS TO USE A CONDITION" FROM WF_MailCondition WHERE IDCondition = S.IDCondition)
AND (S.IDAction = "ACTION FROM OUTSIDE" AND S.IDStatus = "STATUS FROM OUTSIDE")
How to do the SELECT "HERE SELECT 3 COLUMNS..." and if the IDCondition is NULL directly get the columns requested in the first SELECT (Subject, SendTo, etc.)
The "ACTION FROM OUTSIDE" is a parametrized statement.
Forget about foreign keys, we are not using it (boss' decisions...).
I am doing this because is for an "event" thing. I call a method in my C# code that will see if there are mails to send. But this mails can have none, one or more than one conditions. This is to have more options than all have to send a mail. Maybe in the workflow there is no need to send email but in code is "hardcoded" so with this I am totally free to do what I need about that feature.
Maybe all of this can be a function or a stored procedure but I am very ignorant about that and performance with them.
If you need more info, please tell me.
From what I understand, I would write a query like this:
select m.subject, m.cc
from wf_mail m
where exists (select 'true'
from wf_mailstatusaction s, wf_mailcondition c
where s.idmail=m.idmail
and s.idcondition = c.idcondition
and c.idocfield||c.conditionoperator||c.conditionValue = 'id>500'
group by s.idcondition
having count(s.idcondition) = 1)
or exists (select 'true'
from wf_mailstatusaction s, wf_mailcondition c
where s.idmail=m.idmail
and s.idcondition = c.idcondition
and c.idocfield||c.conditionoperator||c.conditionValue IN ('id>500', 'id<200')
group by s.idcondition
having count(s.idcondition) > 1)
or exists (select 'true'
from wf_mailstatusaction s
where s.idmail=m.idmail
and s.idcondition is null)
I would suggest the following:
- Create a QueryBuilder Class
Where you have methods like:
* AddSelect()
* AddFrom()
* AddWhere()
* ToString()
...
And then use the "middlelayer" to handle the query cunstruction based on the conditions
That way you have the most control over everything going on

customize query result

I have 3 tables
tblIndividual
tblIndividualSpeciality
tblSpeciality
One individual have multiple speciality, when I write query I get many rows depends number of speciality to individual.
I need record in single row and each speciality have single column.
This is the table structure
My query
SELECT FirstName,LastName,SpecialtyName
FROM tblindividual ind,tblspecialty spec, tblindividualspecialty indspec
where
ind.pkIndividualID = indspec.fkIndividualID
and spec.SpecialtyCode = indspec.SpecialtyCode
and pkIndividualID = 355735;
I received this format as result
But I need this format as result
Can some one help me please.
I am using MySql database and development tool as Visual Studio 2013.
Part of your problem is that MySQL currently does not have the equivalent of SQL's PIVOT, and in overcoming that it also doesn't have a built-in concept of ROWNUM.
I created your tables with two separate individuals, four specialities of which the first Individual had 3 and the second had 2. I combined these into an interim table (temporary table if you prefer) using
INSERT INTO InterimResults
(
SELECT #row_number:=CASE WHEN #indId=pkIndividualId
THEN #row_number+1 ELSE 1 END AS row_number,
#indId:=pkIndividualId AS pkIndividualId, FirstName, LastName,
SpecialityName
FROM (SELECT #rownum:=0) n,
(SELECT #db_names:='') d,
(SELECT pkIndividualId,FirstName,LastName,SpecialityName
FROM tblindividual ind
inner join tblindividualspeciality indspec on ind.pkIndividualId=indspec.fkIndividualId
inner join tblspeciality spec on spec.pkSpecialityCode=indspec.fkSpecialityCode) r
);
This gave me the following results (with my test data). Note if you run this in SQLFiddle's "Build Schema" pane all of the row numbers come out as 1, but the query works correctly in the "Run Sql" panel.
row_number pkIndividualId FirstName LastName SpecialityName
1 1 Test I-Health Internal Medecine
2 1 Test I-Health Family Practise
3 1 Test I-Health Cardiology
1 2 Test2 J-Health Family Practise
2 2 Test2 J-Health Obstetrics
It is then quite simple to "pivot" this interim table using GROUP_CONCAT
SELECT
FirstName, LastName,
GROUP_CONCAT(if(row_number = 1, SpecialityName, NULL)) AS 'Speciality1',
GROUP_CONCAT(if(row_number = 2, SpecialityName, NULL)) AS 'Speciality2',
GROUP_CONCAT(if(row_number = 3, SpecialityName, NULL)) AS 'Speciality3'
FROM InterimResults
GROUP BY pkIndividualId;
References I used:
http://www.artfulsoftware.com/infotree/qrytip.php?id=78
and http://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/
Of course this assumes that you only want to report on the first 3 specialities, or each individual can only have up to 3 specialities - if there are others then you will need to use Prepared statements and dynamic sql - there is an example at http://stratosprovatopoulos.com/web-development/mysql/pivot-table-with-dynamic-columns/
Use PIVOT query.
above link is for your reference.
http://forums.asp.net/t/1851916.aspx?Converting+Column+values+to+Rows+in+SQL+Query
This link is helpful for you.
I hope you solved your problem.
SELECT FirstName, LastName, SpecialtyName
FROM tblindividual T1,tblspecialty T2, tblindividualspecialty T3
WHERE T1.pkIndividualID = T3.fkIndividualID AND T2.SpecialtyCode =T3.SpecialtyCode AND pkIndividualID = 355735;

How to do Searching and Filtering Data based on Checkboxes in Many to Many Relationship in SQL?

Basically i have 3 tables like this (with many to many relationship);
And i am querying searching like this;
ALTER PROC [dbo].[usp_ContactSearch]
(
#PersonName varchar(60)= '',
#MobileNo varchar(20)= '',
#Nationlity varchar(50)='' ,
#ContactTypes varchar(max) = ''
)
AS
BEGIN
SELECT DISTINCT c.ContactId, c.PersonName, ct.ContactType, ct.ContactTypeId
FROM Contact c
LEFT OUTER JOIN ContactWithContactType cct
ON c.ContactId = cct.ContactId
LEFT OUTER JOIN ContactType ct
ON cct.CountactTypeId = ct.ContactTypeId
WHERE
c.PersonName LIKE CASE WHEN #PersonName='' THEN c.PersonName ELSE '%'+#PersonName+'%' END
AND
c.MobileNo1 LIKE CASE WHEN #MobileNo='' THEN c.MobileNo1 ELSE '%'+#MobileNo+'%' END
AND
c.Nationality LIKE CASE WHEN #Nationlity='' THEN c.Nationality ELSE '%'+#Nationlity+'%' END
END
So, the result data by default is;
So, from the Front End, i have ContactTypes (which are dynamic i.e comming from contact types table), and the interface looks like this
Now, whenever user check the PropertyOwner(ContactTypeId=1), The data should be filtered and only those contacts should be shown which are belong to ContactTypeId=1
Silarly, when i check the second checkbox i.e Tenant(ContactTypeId=2). The data should be more filtered and only those contacts will be displayed which belongs to ContactTypeId= 1 and 2. And similarly for 3rd ContactType, the data should be more filtered and so on and so forth.
So, the problem is ContactTypes are dynamic and i don't know how to handle this situation.
Any help regards to the query and performance is much apreciated.
Try this. This will work...
-- This is User Defined Table Type Variable
Declare #MyTypeDataType ContType
-- You will pass value to this variable from Front End
Insert into #MyTypeDataType values(1),(2),(3);
-- From Front end you will pass the
-- selected values to "Table Type Variable" and
-- also to a "Varchar" Variable
Declare #Type as Varchar(20);
SET #Type = '1,2,3';
SELECT X.* FROM
(
-- This query will get all persons,
-- who have any one Type u want to Search...
SELECT C.*,CTT.ContactType, CTT.ContactTypeId FROM Contact C
INNER JOIN ContactWithType CT
ON C.ContactId = CT.ContactId
INNER JOIN ContactType CTT
ON CTT.ContactTypeId = CT.ContactTypeId
WHERE #Type LIKE '%' + CAST( CT.ContactTypeId AS VARCHAR(MAX)) + '%'
) X
INNER JOIN
(
-- This will count the Record of each Person,
-- how many time a persons record exists..
SELECT C.ContactId, COUNT(C.ContactId) AS Total
FROM Contact C
INNER JOIN ContactWithType CT
ON C.ContactId = CT.ContactId
INNER JOIN ContactType CTT
ON CTT.ContactTypeId = CT.ContactTypeId
WHERE #Type LIKE '%' + CAST( CT.ContactTypeId AS VARCHAR(MAX)) + '%'
GROUP BY C.ContactId
)Y
ON X.ContactId = Y.ContactId
-- Filter persons
AND Y.Total = (SELECT COUNT(*) FROM #MyTypeDataType)
I would like to recommend to use split function for your query to filter ContactTypes. For example, when in your form someone check Property Owner and Tenant (contactType = 1, 2) or so on, then you can pass it to your stored proc as such
#ContactTypes varchar(max) = '1,2'
Then you can use one of the split string function that you need to create. You can refer to this excellent article (http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings).
I use the article function SplitStrings_Moden for simple string split.
Then you can use it like in your stored proc like this.
And ContactType in (select [item] from SplitStrings_Moden(#ContactTypes , ','))
As for performance wise, the example given return a table of string column which in your case, you could cast it to int for better for performance. But i guess you might to test it on your dataset if the performance is reasonable without the casting.
I would recommend using xml datatype for this. You can find more information here.
Use XML as input variable to an SP

Categories