如何将复杂的SQL转换为LINQ

时间:2019-06-02 15:53:29

标签: c# sql linq

一段时间以来,我一直在尝试将此SQL转换为linq:

SELECT 
    Name
FROM
    Director d
JOIN 
    Movie m ON d.Id = m.DirectorId
JOIN 
    MovieActor ma ON m.Id = ma.MovieId
WHERE 
    ReleaseDate <= '2005-12-31'
    AND Rating >= 9
GROUP BY 
    Name
HAVING 
    COUNT(DISTINCT ma.ActorId) BETWEEN 3 AND 7 
    AND COUNT(DISTINCT CASE WHEN m.DurationMinutes >= 60 THEN m.DurationMinutes END) >= 2
;

这里是100%的工作部分,很简单。

var query4 = (from d in directors
              join m in movies on d.Id equals m.DirectorId
              join ma in movieActors on m.Id equals ma.MovieId
              where m.ReleaseDate <= Convert.ToDateTime("2015-12-31")
              where m.Rating >= 9

              );

大多数情况下,我都在努力解决这些问题:

  1. d, m, ma称为单个表,而不是以某种方式连接3,因为在尝试group d by ... into g时,我丢失了另外两个表。
  2. 我不明白如何计算一个参数(进行条件检查)而不丢失其他参数。

linq中的HAVING语法也不明显。


编辑: 我正在使用LINQ to Objects提供程序,这里有一些: 通过初始化的类,这些类是杂乱的容器

public Director(int _Id, string _Name)
public Movie(int _Id, string _Name, int _DurationMinutes, DateTime _ReleaseDate, int _DirectorId, int _Rating)
public Actor(int _Id, string _Name, int _Age)
public MovieActor(int _MovieId, int _ActorId)

我正在测试的数据(从这些数组中收集列表)

var directorArr = new (int, string)[] { (1, "Bebopvsky"), (2, "Tarrantino"), (3, "CubeRick") };
            var actorArr = new (int, string, int)[] 
            {   (1, "Dat Maan",75), (2, "That Man", 28),
                (3, "Dat Women", 32), (4, "That Women", 22),
                (5, "Already Women", 12) };
            var moviesArr = new (int, string, int, DateTime, int, int)[] 
            {   (1, "Platform for soul", 121, Convert.ToDateTime("2018-12-31"), 2, 9),
                (2, "Full-featured access management", 42, Convert.ToDateTime("2019-01-01"), 3, 7),
                (3, "Robust LDAP server for Java", 13, Convert.ToDateTime("2005-05-25"), 3, 4),
                (4, "Man of Rain", 114, Convert.ToDateTime("2004-07-21"), 1, 10),
                (5, "Man of Arms", 152, Convert.ToDateTime("2003-02-17"), 1, 9),
                (6, "Man of War", 93, Convert.ToDateTime("2017-07-05"), 2, 8),
                (7, "Man of Legs", 33, Convert.ToDateTime("2018-11-11"), 1, 9),
                (8, "Mof", 55, Convert.ToDateTime("2015-11-11"), 2, 8) };
            var movieActorArr = new (int, int)[] 
            {   (1,1), (1,3), (1,4), (1,5),
                (2,1), (2,5),
                (3,4),
                (4,1), (4,2), (4,3), (4,4),
                (5,1), (5,2), (5,3), (5,4), (5,5),
                (6,1), (6,2), (6,3),
                (7,2), (7,4), (7,5),
                (8,1), (8,4) };

3 个答案:

答案 0 :(得分:0)

如果您需要获得导演的姓名,该导演在60分钟内执导了2部或更多电影,每部电影中有3至7名演员参加过(如您在评论中所述),请尝试以下操作:

// use group join to get movies with actors
var moviesWithActors = 
    from m in movies.Where(x => x.ReleaseDate <= new DateTime(2015, 12, 31) && x.Rating >= 9)
    join ma in movieActor on m.MovieId equals ma.MovieId into groupJoin
    let actorsInEachMovieCount = groupJoin.Count()
    where actorsInEachMovieCount > 2 && actorsInEachMovieCount < 8
    select new 
    {
        MovieId = m.MovieId,
    };

// the rest of query
var query = 
    from d in directors
    join m in movies on d.Id equals m.DirectorId
    join ma in moviesWithActors on m.MovieId equals ma.MovieId  // use moviesWithActors from the first query
    group new { 
        Director = d.Name, 
        Movie = m
    } by d.Id into gr
    let moviesDurationOver60MinCount = gr.Select(x => x.Movie).Distinct().Count(x => x.DurationMinutes >= 60)
    where moviesDurationOver60MinCount >= 2     
    select new { DirectorName = gr.First().Director };

此外,可以通过以下方式实现相同目的:

// use group join to get movies with duration > 60 min
var filteredMovies = 
    from d in directors
    join m in movies.Where(x => x.DurationMinutes >= 60 && x.ReleaseDate <= new DateTime(2015, 12, 31) && x.Rating >= 9)
    on  d.Id equals m.DirectorId into groupMovies
    let moviesCountForEachDirector = groupMovies.Count()
    where moviesCountForEachDirector > 1
    select groupMovies;

// use group join to get movies with actors from 3 to 7
var moviesWithActors = 
    from m in filteredMovies.SelectMany(x => x)
    join ma in movieActor on m.MovieId equals ma.MovieId into groupJoin
    let actorsInEachMovieCount = groupJoin.Count()
    where actorsInEachMovieCount > 2 && actorsInEachMovieCount < 8
    select new 
    {
        MovieId = m.MovieId,
    };

// the rest of query
var query = 
    from d in directors
    join m in movies on d.Id equals m.DirectorId
    join ma in moviesWithActors on m.MovieId equals ma.MovieId
    group d by d.Id into gr 
    select new { DirectorName = gr.First().Name };

希望这会有所帮助。

答案 1 :(得分:0)

最后,我想出了解决方案。 首先,我更改了SQL查询(在SO帮助下):

SELECT Name
FROM (SELECT m.Id, d.Name, COUNT(*) as NumActors
      FROM Director d 
           JOIN Movie m
           ON d.Id = m.DirectorId 
           JOIN MovieActor ma
           ON m.Id = ma.MovieId
      WHERE m.ReleaseDate <= '2005-12-31' AND
            m.Rating >= 9 AND
            m.DurationMinutes >= 60
      GROUP BY d.Name, m.Id
      HAVING COUNT(*) BETWEEN 3 AND 7
     ) m 
GROUP BY Name
HAVING COUNT(*) >= 2;

然后在经过图像修改的linq查询中。

var query4 = (from x in 
                 (from d in directors
                     join m in movies2 
                         on d.Id equals m.DirectorId
                     join ma in movieActors 
                         on m.Id equals ma.MovieId
                     where m.ReleaseDate <= Convert.ToDateTime("2005-12-31")
                     where m.Rating >= 9
                     where m.DurationMinutes >= 60
                     group d by new { d.Name, m.Id } into res
                         where res.Count() >= 3
                         where res.Count() <= 7
                         select res)
                 group x by x.Key.Name into fin
                     where fin.Count() >= 2
                     select fin)
                 ;

答案 2 :(得分:0)

这总是让我感到困惑,如果人们有一个需要使用LINQ解决的要求,他们不会给我们要求,而是先将要求转换为SQL,然后给我们提供SQL而不是要求。

在我看来,您想要一个返回以下内容的LINQ语句:

  

请提供曾在某部date或之前导演过电影的所有导演的姓名,这些导演的评分高于或等于某个评分编号,并且导演了至少两部电影的时长至少一个小时,且至少3个,最多7个演员

如果表之间具有一对多或多对多关系,并且您想要“项目及其许多子项”,例如学校及其学生,客户及其订单,订单及其OrderLines,则您应该考虑使用Enumerable.GroupJoin

另一方面,如果您想要带有子项目的子项目,例如,学生拥有他所参加的唯一学校的学生,或拥有该订单的唯一顾客的订单,使用Enumerable.Join

在这种情况下,我们希望导演与他的电影一起使用,所以我们将使用GroupJoin。

TimeSpan oneHour = TimeSpan.FromHours(1);
var result = directors.GroupJoin(movies,
    director => director.Id,              // from each director take the Id
    movie => movie.DirectorId             // from each movie take the DirectorId

    // result selector: take every director with all his movies to make one new:
    (director, moviesOfThisDirector) => new
    {
        // from the director we only need his name
        Name = director.Name

        // From each of his movies we need the ReleaseDate, the Rating and the number of actors
        Movies = moviesOfThisDirector.Select(movie => new
        {
            ReleaseDate = movie.ReleaseDate,
            Rating = movie.Rating,
            Duration = movie.Duration,

            NumberOfActors = movie.GroupJoin(actors,
                movie => movie.Id,
                actor => actor.MovieId,

                // ResultSelector: only count all Actors in this movie
                (movie, actorsInThisMovie) => actorInThisMovie.Count(),
        })
        // well, we don't want all movies,
        // we only want movies before a date with a high rating and a certain duration
        .Where(movie => movie.ReleaseDate <= myDate
                     && movie.Rating >= myRating
                     && movie.Duration >= oneHour),
    })
    // we don't want all Directors, we only want those that have at least one such a movie
    .Where(directory => directory.Movies.Any();

可能的改进

您想按名称对董事进行分组。您确定没有同名董事吗?通过ID加入董事会更快,而且您会知道此ID是唯一的。

您似乎在电影和演员之间设计了一对多的关系:每个电影都有零个或多个演员,每个演员只在一部电影中播放。通常,每个电影都有零个或多个演员,每个演员都播放零个或多个电影:多对多关系,需要连接表。考虑您是否真的想要一对多的关系。