Простой SQL, чтобы проверить, есть ли у родителя дочерние строки или нет - PullRequest
4 голосов
/ 05 ноября 2011

Я показываю сетку с родительскими данными, и мне нужно показать значок, если у него есть соответствующие дочерние строки. Моя БД находится в SQL Server 2008 . Позвольте мне упростить, у меня есть следующие две таблицы -

Заказ (PK: ID)

Файл (ПК: FileID, FK: OrderID)

Order может иметь ноль или более файлов, связанных с ним. Таблица File имеет столбец OrderID, который содержит ссылку FK на Order. Теперь я отображаю сетку, в которой перечислены все Orders, и я хочу отобразить изображение значка, показывающее, есть ли у Order дочерние строки (файлы) или нет.

Вот хитрый способ, который я экспериментировал, но не уверен, насколько он эффективен / масштабируем -

SELECT DISTINCT o.ID, o.OrderNum, ...
,(CASE f.ID/f.ID WHEN 1 THEN 1 ELSE 0 END) as FilesExist
...
FROM  Order AS o LEFT OUTER JOIN dbo.FileHeader as f ON f.OrderID = o.ID

Оператор CASE, кажется, работает идеально, как требуется. Он вернет 1, если существует один или несколько файлов, иначе 0. Если существует несколько строк, то он попытается повторить строку Order, для которой я добавил DISTINCT, и я не выбираю f.ID но f.ID/f.ID, который будет 1 (он существует) и 0 для нуля (не существует). Я узнал, что JOIN лучше, чем встроенные операторы SELECT COUNT(*).

Я проверил, и это работает, но мне нужно мнение эксперта и нужно убедиться, что это лучший способ сделать это. Это очень простой пример, но мой оператор SELECT сложен, так как есть много поисков, и он будет дорогостоящим, поэтому мне нужно убедиться, что он масштабируемый.

Спасибо.

РЕДАКТИРОВАТЬ # 1: В заключение - либо это будет внутренний SELECT с COUNT (*)

SELECT c.ClaimNo,(SELECT COUNT(*) FROM dbo.FileHeader AS f WHERE f.ClaimID = c.ID ) AS FilesHExist
FROM  dbo.Claim AS c

Internal Select

или подход LEFT OUTER JOIN , о котором я упоминал.

SELECT DISTINCT c.ClaimNo, (CASE fh.ID / fh.ID WHEN 1 THEN 1 ELSE 0 END) AS FilesHExist               
FROM  dbo.Claim AS c LEFT OUTER JOIN dbo.FileHeader AS fh ON fh.ClaimID = c.ID

My JOIN approach

Прикреплены два изображения плана выполнения запроса. Пожалуйста, предложите, какой из них лучше.

Ответы [ 3 ]

13 голосов
/ 05 ноября 2011

Для выбора несколько строк это должно быть самым быстрым:

SELECT o.ID
      ,o.OrderNum 
      ,CASE WHEN EXISTS (SELECT * FROM dbo.FileHeader f WHERE f.OrderID = o.ID)
            THEN 1
            ELSE 0
       END AS FilesHExist
FROM   Order o;

Для SELECT, включающего большие порции из dbo.FileHeader, это должно работать лучше:

SELECT o.ID
      ,o.OrderNum 
      ,CASE WHEN f.OrderID IS NULL THEN 0 ELSE 1 END AS FilesHExist
FROM   Order o
LEFT   JOIN
      (SELECT OrderID FROM dbo.FileHeader GROUP BY OrderID) f ON f.OrderID = o.ID

Сначала нужно сгруппировать OrderID, а затем оставить соединение.Нет необходимости в DISTINCT в конце.

Никогда использовать:

(CASE f.ID/f.ID WHEN 1 THEN 1 ELSE 0 END)

Это открывает вам деление на ноль исключений.Замените его проверкой на NULL, как показано выше.

2 голосов
/ 05 ноября 2011

Если вы можете работать с количеством детей, используйте

SELECT o.ID
      ,o.OrderNum
      ,(SELECT COUNT(*) FROM dbo.FileHeader f WHERE f.OrderID = o.ID) AS FilesCount
FROM   Order o;

В противном случае используйте

SELECT o.ID
      ,o.OrderNum
      ,CASE WHEN (SELECT COUNT(*) FROM dbo.FileHeader f WHERE f.OrderID = o.ID) > 0 THEN 1 ELSE 0 END AS FilesExist
FROM   Order o;

Рекомендация:

Всякий раз, когда вы хотите узнать, что на самом деле происходит в БД, сравнивайте ПЛАНЫ для другой версии запроса - в лучшем случае все является обоснованным предположением ..ПЛАН показывает вам, что вы на самом деле будете делать (учитывая, например, сколько строк он ожидает вернуть и другие вещи, о которых мы ничего не говорим при ответе на ваш вопрос о SO).

Предполагая, что выс использованием SQL Server это доступно в SSMS ... то же самое относится и к Oracle - там это доступно в SQL Developer ... большинство БД имеют такую ​​возможность.

0 голосов
/ 05 ноября 2011

Попробуйте это

select A.Id OrderId ,case when isnull(B.FileCounts,0)>0 then 1 else 0 end FilesExist from [Order] A left join (select OrderId, COUNT(1) FileCounts from [File] group by OrderId)B on A.Id=B.OrderId
...