Как превратить комплекс sql в linq - PullRequest
0 голосов
/ 02 июня 2019

Некоторое время я пытался преобразовать этот 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 не очевиден.


Edit: Я использую 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 голосов
/ 02 июня 2019

Наконец-то я придумал решение.Прежде всего, я изменил 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)
                 ;
0 голосов
/ 03 июня 2019

Меня всегда сбивает с толку, что если у людей есть требование, которое им нужно решить с помощью LINQ, они не дают нам требование, а сначала переводят требование в SQL и дают нам SQL вместо требования.

Мне кажется, что вы хотите запрос LINQ, который возвращает следующее:

Дайте мне все имена режиссеров, которые снимали фильмы на или перед определенным date, с оценкойбольше или равно определенному рейтинговому числу, по крайней мере, для 2 фильмов, которые были длиннее, по крайней мере, один час, с по крайней мере 3 и максимум 7 актерами

Если у вас есть один ко многим или многовзаимосвязей между таблицами, и вы хотите, чтобы «Предметы с их многочисленными подэлементами», такие как «Школы с их учениками», «Заказчики с их заказами», «Заказы с их линиями заказов», следует подумать об использовании Enumerable.GroupJoin

Если, с другой стороны, вы хотите получить подпункт с одним элементом, к которому он принадлежит, например, с учеником с единственным единственным School, который он посещает, или Заказ с единственным клиентом, который разместил этот Заказ, используйте 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();

Возможные улучшения

Вы хотите сгруппировать директоров по имени.Вы уверены, что нет директоров с таким именем?Присоединяться к режиссерам по идентификатору будет быстрее, кроме того, вы будете знать, что этот идентификатор уникален.

Похоже, вы разработали отношение «один ко многим» между фильмами и актерами: в каждом фильме есть ноль или более актеров.Каждый актер играет только в одном фильме.Обычно каждый фильм имеет ноль или более актеров, и каждый актер играет в нуле или более фильмах: отношение «многие ко многим», для которого требуется соединительная таблица.Подумайте, действительно ли вы хотите отношения один-ко-многим.

0 голосов
/ 02 июня 2019

Если вам нужно получить имя режиссера, в котором снялись 2 или более фильма в течение 60 минут, в каждом из которых участвовало от 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 };

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...