【问题标题】:How to turn complex sql into linq如何将复杂的sql变成linq
【发布时间】:2019-06-02 15:53:29
【问题描述】:

一段时间以来,我一直在尝试将此 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. 我无法理解如何计算一个参数(进行条件检查)而不丢失其他参数。

另外HAVING linq 中的语法并不明显。


编辑: 我正在使用 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 到 7 名演员,并且有两部电影或更多电影超过 60 分钟?
  • 您使用的是什么 LINQ 提供程序? LINQ 到对象? LINQ To Entity Framework(如果是,那是什么版本的 EF)?您能否发布您的 C# 模型以便我们更轻松地进行复制?
  • @Hogan 导演,导演了 2 部或更多 60 分钟以上的电影,每部有 3 到 7 名演员参与。现在我想,我的 SQL 查询也需要一些更改。
  • @42ama 是的,你的 SQL 错了。

标签: c# sql linq


【解决方案1】:

如果您需要获得导演姓名,该导演的名字在 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 };

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    最后,我想出了解决方案。 首先,我更改了 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)
                     ;
    

    【讨论】:

      【解决方案3】:

      总是让我感到困惑,如果人们有需要使用 LINQ 解决的需求,他们不会给我们需求,而是先将需求翻译成 SQL,然后给我们 SQL 而不是需求。

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

      请给我所有导演的姓名,他们在某个date 或之前导演过电影,评分高于或等于某个评分,至少有 2 部电影时长至少一小时,至少3个,最多7个演员

      如果您在表之间存在一对多或多对多关系,并且您想要“项目及其许多子项目”,例如学校及其学生、客户及其订单、订单及其订单行,您应该考虑使用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 是唯一的。

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

      【讨论】:

      • move.DirectorId 后面少了一个逗号,两个地方多了一个逗号。您的代码示例无法编译。
      猜你喜欢
      • 2018-07-31
      • 2016-08-19
      • 1970-01-01
      • 2018-02-08
      • 1970-01-01
      • 1970-01-01
      • 2020-09-29
      • 2019-02-06
      • 2011-03-19
      相关资源
      最近更新 更多