Transform SQL query with recursion into LINQ - c#

I'm stuck on a task where I have to transform a Stored Procedure into a LINQ query.
The model:
AccountSet: Account table with columns 'AccountId', 'ParentAccountId' (references an 'AccountId') and 'Name'
ContactSet: Contact table with columns 'ParentCustomerId'
(references an Account via 'AccountId')
The Stored Procedure:
It should search for all accounts with the given id
Search all parents (recursive) for the accounts found in step 1
Fetch all contacts that have a ParentCustomerId matching an 'AccountId' found in step 2
CREATE PROCEDURE [dbo].[sp_GetContactsForCompany]
(
#projectid AS UNIQUEIDENTIFIER
)
AS WITH recursion ( AccountId, Name, ParentAccountId )
AS (
SELECT AccountId, Name, ParentAccountId
FROM dbo.AccountBase
WHERE AccountId = #projectid
UNION ALL
SELECT a.AccountId, a.Name, a.ParentAccountId
FROM dbo.AccountBase AS a
INNER JOIN recursion AS b ON a.ParentAccountId = b.AccountId
)
SELECT ContactId, FullName
FROM dbo.ContactBase
WHERE ParentCustomerId IN (
SELECT AccountId
FROM recursion
)
ORDER BY FullName
LINQ:
from a in allAccs
where a.AccountId == id
select a;
This gives me all the accounts with the given id. But now I have no idea how to apply the join and recursion.
Any hint would be great.

Related

Run/Convert raw SQL Hierachy using/to Linq?

I have the following query that works fine (ms-sql db), but cannot figure out how to run it using a Linq query. Also I could run it directly in Linq but have no idea in how to convert it to a Linq expression. Im trying to get all the subordinates for a employee all the way down the management tree. The table (in brief) is set up like this:
Emp_ID => Guid,
Reports_To => Guid, (The Emp_ID of their manager)
Name_Last, Name_First, etc.....
with Hierachy(Emp_ID, Reports_To, Name, Level)
as
(
select Emp_ID, Reports_To, Name_Last, 0 as Level
from Employees c
where c.Emp_ID = '5287E80D-8169-4D24-BABD-7639B1D68D59' ***This should also be a parameter, hard coded here**
union all
select c.Emp_ID, c.Reports_To, c.Name_Last, ch.Level + 1
from Employees c
inner join Hierachy ch
on c.Reports_To = ch.Emp_ID
)
select Emp_ID, Reports_To, Name
from Hierachy
where Level > 0
Thanks!

How to construct SQL query where field IN sub query?

I am working with the Yelp data-set available online. I've been trying to optimize my query for days. For the schema I'll list below, I need to construct a query to provide the following:
Given a user's UID, display the most recent review information for each of the user's friends.
Here's the schema:
CREATE TABLE business(
bid varchar(40) PRIMARY KEY,
name varchar(100),
city varchar(40),
state char(2),
zip varchar(10),
latitude real,
longitude real,
address varchar(100),
numreviews INTEGER DEFAULT 0,
numcheckins INTEGER DEFAULT 0,
avgreview float DEFAULT 0,
isopen bool,
stars float
);
CREATE TABLE users(
uid varchar(40) PRIMARY KEY,
name varchar(40),
avgstars float,
fans INTEGER,
coolvotes INTEGER,
reviewcount INTEGER,
funnyvotes INTEGER,
signup varchar(20),
usefulvotes INTEGER,
latitude real,
longitude real
);
CREATE TABLE reviews(
rid varchar(40) PRIMARY KEY,
bid varchar(40),
uid varchar(40),
stars float,
date varchar(20),
funny INTEGER,
useful INTEGER,
cool INTEGER,
text varchar(1024),
FOREIGN KEY (uid) REFERENCES users(uid),
FOREIGN KEY (bid) REFERENCES business(bid)
);
CREATE TABLE friends(
uid varchar(40) REFERENCES users(uid),
fid varchar(40) REFERENCES users(uid)
);
Here's an example of the desired output:
For each of the user's friends, I display the following:
The friend's name
The name of the business from their most recent review
The city of the business from their most recent review
The text from their most recent review
Currently this is the only "solution" I've had success with.
Step 1: Get a list of all of the IDs for each of the user's friends.
SELECT fid from friends where uid = '{userId}'
This returns a list of all of the user ID's for each of the user's friends. So I basically have a friend ID list.
Step 2: With this information, I run a foreach loop in my program over that list. For each iteration of the friend ID list, I execute the below query and provide the temporary friend ID for the current iteration of the loop:
SELECT U.name, B.name, B.city, R.text, R.date FROM reviews as R, users as U, business as B
WHERE U.uid = '{currentFriendId}'
AND R.uid = '{currentFriendId}'
AND B.bid = R.bid
AND date = (SELECT MAX(date) FROM reviews WHERE uid = '{currentFriendId}')
For EACH time I run this for loop, I get a single line of output for what I desire, such as this:
This is great...except I have to run this query for every single one of the user's friends. This is extremely costly.
Goal: I'm trying to combine these 2 queries, or revamp them completely, to generate all of the rows at once in a single query.
Question: Given the information provided, how can I fix my queries to generate all of this information from a single query?
It looks like a top-n-per-group problem.
One way to do it is to use a lateral join.
Make sure you have an index on reviews table on (uid, date). A composite index. One index on two columns in this order.
Something like this:
CREATE INDEX IX_uid_date ON reviews (uid, date);
Query
SELECT
t.UserName
,t.BusinessName
,t.city
,t.text
,t.date
FROM
friends
INNER JOIN LATERAL
(
SELECT
users.name AS UserName
,business.name AS BusinessName
,business.city
,reviews.text
,reviews.date
FROM
reviews
INNER JOIN users ON users.uid = reviews.uid
INNER JOIN business ON business.bid = reviews.bid
WHERE
reviews.uid = friends.fid
ORDER BY reviews.date DESC
LIMIT 1
) AS t ON true
WHERE
friends.uid = '{userId}'
;
This should work fine.
SELECT name FROM employees as E
WHERE E.uid IN (SELECT uid FROM employees WHERE name = 'John')
You do not need to do an equal comparison as in single truth value match queries.
Following up on Manos' answer, not sure I understand why you need to limit each fid at all
SELECT U.name, B.name, B.city, R.text, R.date
FROM business AS B
INNER JOIN reviews AS R ON B.bid = R.bid
INNER JOIN users AS U ON R.uid = U.uid
WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE X.uid = R.uid))
AND (R.uid IN (SELECT fid FROM friends));
If your issue is that your query only results in one row, you should remove that where uid = to get results for all uid's.
I have also arrived at an answer the same time roughly as Vladimir Baranov, but I will post my version as well. I don't promise it to be pretty:
SELECT R.name as user_name, B.name as business_name, B.City, R.text
FROM (SELECT bid, name, text
FROM (SELECT R.rid, R.bid, R.uid, R.text, max_date
FROM reviews as R INNER JOIN
(SELECT uid, MAX(date) as max_date FROM reviews WHERE uid IN (SELECT fid from friends where uid = 'BfcNxKpnF9z5wJLXY7elRg') GROUP BY uid) sub
ON R.uid = sub.uid AND R.date = sub.max_date) as review_info
INNER JOIN users
on review_info.uid = users.uid) as R
INNER JOIN business as B
ON R.bid = B.bid
After examining the schema you posted, I used MySQL to create the database and populate the tables with the following sample data:
INSERT INTO users (uid, name) VALUES
('user1', 'user1 name'),
('user2', 'user2 name'),
('user3', 'user3 name'),
('user4', 'user4 name'),
('user5', 'user5 name');
INSERT INTO friends (uid, fid) VALUES
('user1', 'user2'), ('user1', 'user3'),
('user2', 'user4'), ('user2', 'user5');
INSERT INTO business (bid, name, city) VALUES
('b1', 'business 1', 'city 1'),
('b2', 'business 2', 'city 2'),
('b3', 'business 3', 'city 3'),
('b4', 'business 4', 'city 4');
INSERT INTO reviews (rid, bid, uid, stars, date, text) VALUES
('r1', 'b1', 'user1', 5, '2019-05-01', 'blah'),
('r2', 'b2', 'user1', 5, '2019-05-02', 'blah'),
('r3', 'b3', 'user1', 5, '2019-05-03', 'blah'),
('r4', 'b1', 'user2', 4, '2019-05-11', 'blah'),
('r5', 'b2', 'user3', 3, '2019-05-12', 'blah'),
('r6', 'b1', 'user4', 5, '2019-05-13', 'blah');
This allowed me to verify that the original solution I proposed was correct by executing the query in MySQL Workbench.
I assume that the 'failure to finish' you mention has nothing to do with the query per se, but is rather a temporary failure of the DB connection api you use.
Note that the code is updated to incorporate Mihail Shishkov's proposal for using parameters.
-- Display review information originating from friends of user1
-- DECLARE #UID varchar(40); -- Uncomment for MS-SQL (variables need to be declared)
SET #UID = 'user1';
SELECT U.name, B.name, B.city, R.text, R.date
FROM business AS B
INNER JOIN reviews AS R ON B.bid = R.bid
INNER JOIN users AS U ON R.uid = U.uid
WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE (X.uid = R.uid)))
AND (R.uid IN (SELECT F.fid FROM friends AS F WHERE (F.uid = #UID)));
Based on the sample data and using 'user1' as the value for the #UID parameter, the results of the query are:
name name city text date
------------------------------------------------
user2 name business 1 city 1 blah 2019-05-11
user3 name business 2 city 2 blah 2019-05-12
Moreover, I assume that friendship is a two-way relationship in the context of your schema (as in the real world), meaning that friendship between 'user1' and 'user2' only needs to be defined by a single record in table 'friends' with the values ('user1', 'user2') and the reverse ('user2', 'user1') is unnecessary.
So, for the sake of completeness, you can use the following query:
-- Display review information originating from friends of user2
SET #UID = 'user2';
SELECT U.name, B.name, B.city, R.text, R.date
FROM business AS B
INNER JOIN reviews AS R ON B.bid = R.bid
INNER JOIN users AS U ON R.uid = U.uid
WHERE (R.date = (SELECT MAX(X.date) FROM reviews AS X WHERE (X.uid = R.uid)))
AND (R.uid IN (SELECT F.fid FROM friends AS F WHERE (F.uid = #UID) UNION
SELECT F.uid FROM friends AS F WHERE (F.fid = #UID)));
Now, using 'user2' as the value for the #UID parameter and the extended version of the query, we obtain the following results:
name name city text date
------------------------------------------------
user1 name business 3 city 3 blah 2019-05-03
user4 name business 1 city 1 blah 2019-05-13
I would appreciate it if you acknowledge the answer as acceptable.

Need an approach to get the record id from a table with hierarchy data

I need some help in getting a record id from a table. Basically I'm working on project where the folders and files of a particular path are stored in database.
It includes a desktop and windows service applications and I'm using a SDF file for database and handling data operations using ADO.NET using C#
This is my folders table
As you can see, its a hierarchy table and FolderId is an identity column.
Now suppose my data is as
And I have a path as "E:\Books\WCF\Examples.pdf". Now how can I get the FolderId of "Examples.pdf" file from above table.
I came up with following approaches
Approach 1:
To get all the records from Folders table which match with the folder name as "WCF" along with their complete hierarchy by writing a recursive method. So I will get following data
Now in my code, I will be comparing this hierarchy column with above folder path of the pdf to get the FolderId.
Approach 2:
Taking each and every folder from the pdf path, I will generate dynamic query which looks something like this
select FolderId from Folders where Name='WCF' and
ParentFolderId in (select FolderId from Folders where Name='Books' and
ParentFolderId in (select FolderId from Folders where Name='E:'))
Based on my two approaches, which one should I prefer. Performance is a crucial factor and the Folders table may have more than a million records. Feel free to suggest any better approach.
Test Data
CREATE TABLE Table_Name50
(ID INT, Name nvarchar(100), ParentID INT)
GO
INSERT INTO Table_Name50(ID, Name,ParentID)
VALUES
( 1 ,N'E:\',NULL),
( 2, N'Books', 1),
( 3, N'History', 2),
( 4, N'Biology', 2),
( 5, N'Vidoes', 1)
GO
Query
to pull the full path for a given ID
DECLARE #ID INT = 3
;with CompleteData
as
(
Select ID, ParentId from Table_Name50
UNION
Select Child.ParentID Id, Parent.ParentID ParentId From Table_Name50 Child
Left Outer Join Table_Name50 Parent
on Child.ParentID = parent.ID
WHERE
parent.ParentID IS NULL
),
ChildHierarchyData(ID,ParentID, Level)
as
(
Select ID,ParentID, 0 as Level from CompleteData Where ID = #ID --<-- Your Parameter
union all
Select CompleteData.ID, CompleteData.ParentID, ChildHierarchyData.Level +1 from CompleteData
INNER Join ChildHierarchyData
on ChildHierarchyData.ParentID = CompleteData.ID
),
Concatinated(result)
as
(
Select Cast((select Cast(Name as nvarchar) + '\' [data()]
from ChildHierarchyData CD INNER JOIN Table_Name50 tbl
ON CD.ID = tbl.ID
Order By Level Desc
FOR XML Path('')) as Nvarchar(max))
)
select Left(result, len(result)-1) as Result from Concatinated
Result
E:\\ Books\ History

SQL : Compound Subset Select

LAYOUT:
I have a Subscriber database with Subscriber info in a table, all with unique AccountID's.
I have multiple History databases with a History table in each, all pertaining to the AccountID's in the Subscriber database.
I NEED:
I need a list of the most recent History record entered, in any of the History databases, for each AccountID in the Subscriber data. 1 record per AccountID.
I can achieve this with multiple hits to the database, but there are potentially millions of records and that doesn't sit well in my head. I want to make this happen in one hit.
Help. Me. Thanks.
Here's something I have tried already, but it doesn't give me a single record per AccountID...
SELECT
MAIN.*,
ISNULL(SubData.Name, '') AS [Name],
ISNULL(SubData.AcctLineCode, '') AS AcctLineCode,
ISNULL(LTRIM(RTRIM(SubData.AcctNum)), '') AS AcctNum
FROM
(
SELECT AccountID, AlarmDate, AlarmCode FROM [History1113]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
UNION
SELECT AccountID, AlarmDate, AlarmCode FROM [History1013]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
UNION
SELECT AccountID, AlarmDate, AlarmCode FROM [History0913]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
)
AS MAIN
LEFT JOIN Subscriber..[Subscriber Data] AS SubData ON Main.AccountID = SubData.AccountID
ORDER BY AccountID, AlarmDate DESC
I'd do it as a view. Biggest issue will be making sure the view can see all the history tables if they are in seperate databases. You may have to get into linked servers
Create view historytable
as
select * from historytable1
union all
select * from historytable2
union all
etc...
Now query from historytable as if it was a table with all rows in it.
Edit:
the statement you've added has no aggregates, so it has no method of filtering down (or grouping by) into one record.
To your reply:
Lets call my view above main so I don't have to type so much.
Select account_id, max(alarm_date) as maxdate from main group by account_id
This simple select brings back to most recent record. Inner join it so it functions as a filter.
select ...
from main
inner join (Select account_id, max(alarm_date) as maxdate from main group by account_id) maxdate
on main.account_id = maxdate.account_ID and maxdate.maxdate = main.alarm_date
Add your subscriber join to the bottom of that and fill in the columns you need
With a little help from a couple of you, I was able to figure this out. So, thank you all.
Here's a code snippet of how I got it to work. I still need to do some joins to bring in account info, but this was the hard part.
`
SELECT MAIN.AccountID, MAX(MAIN.AlarmDate) AS AlarmDate FROM
(
SELECT AccountID, MAX(AlarmDate) AS AlarmDate FROM [History1113]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
GROUP BY AccountID
UNION
SELECT AccountID, MAX(AlarmDate) AS AlarmDate FROM [History1013]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
GROUP BY AccountID
UNION
SELECT AccountID, MAX(AlarmDate) AS AlarmDate FROM [History0913]..SignalHistory WHERE AccountID IN (SELECT DISTINCT AccountID FROM Subscriber..[Subscriber Data])
GROUP BY AccountID
)
AS MAIN
GROUP BY MAIN.AccountID
`

SQL Query in c#

I might have a problem with my SQL query. In this query I'm combining 4 different tables.
I have a table courses where general information is stored (course_number, course_title).
I have a table employees where general information of employees isstored (empname, and a job_id).
A employee has a job. A employee needs to take courses. It depends on the job which courses he has to take. This info is stored in the table job_course (with the job_id and the course_id).
If a employee completed a course it is stored in the table emp_courses (with the e_id and the course_id)
Now I want to search a certain course - when the user presses the search button he should get two different results.
The first one: here you can see which employee already took this course (this query works so far)
the second one: here you can see which employee still needs to take the course. So i need to check which job the employee has and if he needs to make that course . and also i just want to have the ones that are not completed yet.
And that's the query that is not working
Here it is:
OpenDb_Open("select course_number,course_title, empname from course
INNER JOIN (job_course INNER JOIN (employee INNER JOIN emp_course
ON emp_course.e_id<>employee.e_id) ON job_course.job_id=employee.job_id)
ON course.course_id=job_course.course_id
where course_number like '" + coursenumber + "'");
Can someone please help me with this?
Courses the employee hasn't taken.
SELECT * FROM courses
WHERE course_number IN (
SELECT course_id FROM job_course
WHERE course_id NOT IN (
SELECT course_id FROM emp_courses
WHERE emp_id = {someid}
) AND job_id = (
SELECT job_id FROM employees
WHERE emp_id = {user_input}
)
)
Which employees still need to take a course.
SELECT emp_name FROM employees
WHERE emp_id NOT IN (
SELECT emp_id FROM emp_courses
WHERE course_id = {user_input}
)
Variant of above.
SELECT emp_name FROM employees
WHERE emp_id NOT IN (
SELECT emp_id FROM emp_courses
WHERE course_id = (
SELECT course_id FROM courses
WHERE course_number = {user_input}
)
)

Categories

Resources