{"id":91,"date":"2015-11-02T10:08:21","date_gmt":"2015-11-02T10:08:21","guid":{"rendered":"https:\/\/sqldoubleg.live-website.com\/?p=91"},"modified":"2015-11-02T10:11:56","modified_gmt":"2015-11-02T10:11:56","slug":"day-1-adventureworks-the-solutions","status":"publish","type":"post","link":"https:\/\/www.sqldoubleg.com\/es\/2015\/11\/02\/day-1-adventureworks-the-solutions\/","title":{"rendered":"Day 1 @ AdventureWorks, the Solutions"},"content":{"rendered":"<p>Solutions for Day 1 of exercises using the AdventureWorks sample database&nbsp;It&#8217;s been a while since I published <a href=\"https:\/\/sqldoubleg.live-website.com\/2015\/09\/14\/day-1-adventureworks-some-basics-about-select-joins-and-more\/\" target=\"_blank\">Day 1 questions<\/a> and I guess it&#8217;s time to post the answers I came up with.<br \/>\nThat doesn&#8217;t mean my solutions are the only possibles, and in some cases I&#8217;ll provide more than one answer taking different approaches.<\/p>\n<p><strong>The Answers<\/strong><\/p>\n<p>The first question does not have much to talk about, it&#8217;s just a matter of locate the table we want to retrieve the data from and select the columns we&#8217;re asked to.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--01=================================================================================\r\n-- Retrieve a list of the different types of contacts that can exist in the database \r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- Name\r\n\r\nSELECT ContactTypeID, Name \r\n\tFROM Person.ContactType\r\n<\/pre>\n<p>The Second is based on the first and will use the WHERE clause to filter the results returned by the query.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--02=================================================================================\r\n-- Retrieve (ContactTypeID, Name) for the contact type 'Marketing Manager'\r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- Name\r\nSELECT ContactTypeID, Name \r\n\tFROM Person.ContactType\r\n\tWHERE Name = 'Marketing Manager'\r\n<\/pre>\n<p>Third we&#8217;ll need to use the WHERE clause combined with the <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms179859.aspx\" target=\"_blank\">logical operator LIKE<\/a> to filter the different names that contain the word <\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--03=================================================================================\r\n-- Retrieve a list of the different types of contacts which are managers\r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- Name\r\nSELECT ContactTypeID, Name \r\n\tFROM Person.ContactType\r\n\tWHERE Name LIKE '%Manager%'\r\n<\/pre>\n<p>Next question adds another clause, this time is <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188385.aspx\" target=\"_blank\">ORDER BY<\/a>,  which sorts the returned results.<br \/>\nIt&#8217;s important to remind <\/p>\n<blockquote><p>The order in which rows are returned in a result set are not guaranteed unless an ORDER BY clause is specified.<\/p><\/blockquote>\n<p>Sounds pretty obvious but it&#8217;s awesome how many people will fail to know it, even experienced professionals.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--04=================================================================================\r\n-- Retrieve a list of the different types of contacts which are managers\r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- Name\r\n-- Sorted by\r\n--\t\t\t- Alphabetically Descending\r\nSELECT ContactTypeID, Name \r\n\tFROM Person.ContactType\r\n\tWHERE Name LIKE '%Manager%'\r\n\tORDER BY Name DESC\r\n<\/pre>\n<p>Retrieving data from more than one table requires a more elaborated query, where we specify the tables involved and the equality columns for each of the JOIN operators.<br \/>\nIn this SELECT, we need to retrieve only columns from one of the tables, [Person].[Person], but since we need to filter only those which are &#8216;Purchasing Manager&#8217; we need somehow to involve [Person].[ContactType] in the query. <\/p>\n<p>The table which we can use both is [Person].[BusinessEntityContact] which contains Foreign Keys referencing both tables.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--05=================================================================================\r\n-- Retrieve a list of all contacts which are 'Purchasing Manager' \r\n-- Output: \r\n--\t\t\t- BusinessEntityID\r\n--\t\t\t- LastName\r\n--\t\t\t- FirstName\r\n--\t\t\t- MiddleName\r\n--\r\n-- Sorted by:\r\n--\t\t\t- LastName\t\tascending\r\n--\t\t\t- FirstName\t\tascending\r\n--\t\t\t- MiddleName\tascending\r\n\r\nSELECT p.BusinessEntityID, LastName, FirstName, MiddleName\r\n\tFROM Person.BusinessEntityContact AS bec\r\n\t\tINNER JOIN Person.ContactType AS c\r\n\t\t\tON c.ContactTypeID = bec.ContactTypeID\r\n\t\tINNER JOIN Person.Person AS p\r\n\t\t\tON p.BusinessEntityID = bec.PersonID\r\n\tWHERE c.Name = 'Purchasing Manager'\r\n\tORDER BY LastName, FirstName, MiddleName\r\n\r\n<\/pre>\n<p>Next one is built on top of the previous again. You have seen that there&#8217;s a number of people which do not have MiddleName and displays as NULL.<br \/>\nTo avoid this, we can use the TSQL function ISNULL(), to replace NULL values for the value we want, in this case an empty string \u00bb.<br \/>\nAlso we need to display the name of the column as MiddleName, hence we need to use an alias for it.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--06=================================================================================\r\n-- Retrieve a list of all contacts which are 'Purchasing Manager' \r\n-- Output: \r\n--\t\t\t- BusinessEntityID\r\n--\t\t\t- LastName\r\n--\t\t\t- FirstName\r\n--\t\t\t- MiddleName, if there is no MiddleName, to display '' (empty string) instead of NULL\r\n--\r\n-- Sorted by:\r\n--\t\t\t- LastName\t\tascending\r\n--\t\t\t- FirstName\t\tascending\r\n--\t\t\t- MiddleName\tascending\r\n\r\nSELECT p.BusinessEntityID, LastName, FirstName, ISNULL(MiddleName,'') AS MiddleName\r\n\tFROM Person.BusinessEntityContact AS bec\r\n\t\tINNER JOIN Person.ContactType AS c\r\n\t\t\tON c.ContactTypeID = bec.ContactTypeID\r\n\t\tINNER JOIN Person.Person AS p\r\n\t\t\tON p.BusinessEntityID = bec.PersonID\r\n\tWHERE c.Name = 'Purchasing Manager'\r\n\tORDER BY LastName, FirstName, MiddleName\r\n\r\n<\/pre>\n<p>We need now to count the number of contacts for each type and display it along the ContactTypeID and ContactTypeName<br \/>\nTo do so, we need to use <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms175997.aspx\" target=\"_blank\">COUNT()<\/a>, which is an aggregate function and in this case requires a <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms177673.aspx\" target=\"_blank\">GROUP BY<\/a> clause to return one single row for each group.<br \/>\nAgain we need to use an alias to name the column.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--07=================================================================================\r\n-- Retrieve a list of the different types of contacts and how many of them exist in the database \r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- ContactTypeName\r\n--\t\t\t- N_contacts\r\n--\r\n\r\nSELECT c.ContactTypeID, c.Name AS ContactTypeName, COUNT(*) AS N_contacts\r\n\tFROM Person.BusinessEntityContact AS bec\r\n\t\tINNER JOIN Person.ContactType AS c\r\n\t\t\tON c.ContactTypeID = bec.ContactTypeID\r\n\tGROUP BY c.ContactTypeID, c.Name\r\n\t\r\n<\/pre>\n<p>Just add the ORDER BY clause to complete the next question<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--08=================================================================================\r\n-- Retrieve a list of the different types of contacts and how many of them exist in the database \r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- ContactTypeName\r\n--\t\t\t- N_contacts\r\n--\r\n-- Sorted by:\r\n--\t\t\t- N_contacts descending\r\n\r\nSELECT c.ContactTypeID, c.Name AS ContactTypeName, COUNT(*) AS N_contacts\r\n\tFROM Person.BusinessEntityContact AS bec\r\n\t\tINNER JOIN Person.ContactType AS c\r\n\t\t\tON c.ContactTypeID = bec.ContactTypeID\r\n\tGROUP BY c.ContactTypeID, c.Name\r\n\tORDER BY COUNT(*) DESC\r\n\r\n<\/pre>\n<p>We have seen before that to filter the returned rows we can use the WHERE clause, but to filter rows based on the aggregate function we must use the <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms180199.aspx\" target=\"_blank\">HAVING<\/a> clause.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--09=================================================================================\r\n-- Retrieve a list of the different types of contacts and how many of them exist in the database \r\n-- Output: \r\n--\t\t\t- ContactTypeID\r\n--\t\t\t- ContactTypeName\r\n--\t\t\t- N_contacts\r\n--\r\n-- Sorted by:\r\n--\t\t\t- N_contacts descending\r\n-- Filter:\r\n--\t\t\t- Only interested in ContactTypes that have 100 contacts or more.\r\n\r\nSELECT c.ContactTypeID, c.Name AS ContactTypeName, COUNT(*) AS N_contacts\r\n\tFROM Person.BusinessEntityContact AS bec\r\n\t\tINNER JOIN Person.ContactType AS c\r\n\t\t\tON c.ContactTypeID = bec.ContactTypeID\r\n\tGROUP BY c.ContactTypeID, c.Name\r\n\tHAVING COUNT(*) &gt;= 100\r\n\tORDER BY COUNT(*) DESC\r\n<\/pre>\n<p>More functions into play, in order to comply with the requirements, we will need to use a function to generate the column full name and another to format the date to the required, also some calculation must be done to calculate the weekly salary as in the database we only store the hourly rate.<\/p>\n<ul>\n<li>For the name, I used <a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/hh231515.aspx\" target=\"_blank\">CONCAT<\/a><\/li>\n<li>To format the date, I used <a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/ms187928.aspx\" target=\"_blank\">CONVERT<\/a>, specifying the format, you can check all the possibilities by yourself<\/li>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--10=================================================================================\r\n--\r\n-- The table [HumanResources].[EmployeePayHistory] holds employees current salary along with historical data\r\n--\r\n--\r\n-- List of employees and their Historical weekly salary (based on 40h a week) \r\n-- Output: \r\n--\t\t\t- RateChangeDate from historical table aliased to DateFrom, in the format 'dd\/mm\/yyyy'\r\n--\t\t\t- 1 column which includes 'LastName, FirstName MiddleName' called FullNamne \r\n--\t\t\t- WeeklySalary which must be calculated too\r\n--\r\n-- Sorted by \r\n--\t\t\t- FullName \r\n\r\nSELECT CONVERT(VARCHAR, h.RateChangeDate, 103) AS DateFrom\r\n\t\t, CONCAT(LastName, ', ', FirstName, ' ', MiddleName) AS FullName\r\n\t\t, (40 * h.Rate) AS WeeklySalary\r\n\tFROM Person.Person AS p\r\n\t\tINNER JOIN HumanResources.EmployeePayHistory AS h\r\n\t\t\tON h.BusinessEntityID = p.BusinessEntityID\t\t\r\n\tORDER BY FullName\r\n<\/pre>\n<p>The Bonus question gets a bit more complicated as you&#8217;d expect. More concepts apply here depending on the approach we want to take.<\/p>\n<p>In words, we need to find the most recent date (MAX) each person has had his\/her salary changed.<\/p>\n<p>The first solution I came up with is to use a <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms187638.aspx\" target=\"_blank\">correlated subquery<\/a> which filters by the maximum date for each person.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n--===================================================================================\r\n-- BONUS\r\n--===================================================================================\r\n\r\n\r\n--===================================================================================\r\n-- List of employees and their current weekly salary (based on 40h a week) \r\n-- Output: \r\n--\t\t\t- RateChangeDate from historical table aliased to DateFrom, in the format 'dd\/mm\/yyyy'\r\n--\t\t\t- 1 column which includes 'LastName, FirstName MiddleName' called FullNamne \r\n--\t\t\t- WeeklySalary which must be calculated too\r\n--\r\n-- Sorted by \r\n--\t\t\t- FullName \r\n\r\n--\r\n-- ONLY 1 line per Employee, the current one to be precise.\r\n\r\n-- Using subquery in the WHERE clause\r\n\r\nUSE AdventureWorks2014\r\n\r\nSELECT CONVERT(VARCHAR,(h.RateChangeDate),103) AS DateFrom\r\n\t\t, CONCAT(LastName, ', ', FirstName, ' ', MiddleName) AS FullName \r\n\t\t, (40 * h.Rate) AS WeeklySalary\r\n\tFROM Person.Person AS p\r\n\t\tINNER JOIN HumanResources.EmployeePayHistory AS h\r\n\t\t\tON h.BusinessEntityID = p.BusinessEntityID\r\n\tWHERE h.RateChangeDate = (SELECT MAX(RateChangeDate) \r\n\t\t\t\t\t\t\t\tFROM HumanResources.EmployeePayHistory \r\n\t\t\t\t\t\t\t\tWHERE BusinessEntityID = h.BusinessEntityID)\r\n\tORDER BY FullName\r\n<\/pre>\n<p>We can use a subquery and do INNER JOIN to get the desired rows. Here we also need to use the GROUP BY clause to get the MAX date for each employee, not just the maximum date in general.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n-- Using subquery in the INNER JOIN\r\nSELECT CONVERT(VARCHAR,(h.RateChangeDate),103) AS DateFrom\r\n\t\t, CONCAT(LastName, ', ', FirstName, ' ', MiddleName) AS FullName \r\n\t\t, (40 * h.Rate) AS WeeklySalary\r\n\tFROM Person.Person AS p\r\n\t\tINNER JOIN HumanResources.EmployeePayHistory AS h\r\n\t\t\tON h.BusinessEntityID = p.BusinessEntityID\r\n\t\tINNER JOIN (SELECT BusinessEntityID, MAX(RateChangeDate) AS RateChangeDate \r\n\t\t\t\t\t\tFROM HumanResources.EmployeePayHistory\r\n\t\t\t\t\t\tGROUP BY BusinessEntityID) AS Cur\r\n\t\t\tON cur.BusinessEntityID = h.BusinessEntityID \r\n\t\t\t\tAND cur.RateChangeDate = h.RateChangeDate\r\n\tORDER BY FullName\r\n\r\n<\/pre>\n<p>The inner join can be replaced with <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms175156.aspx\" target=\"_blank\">CROSS APPLY<\/a>, but since it doesn&#8217;t have ON clause, we need to filter the subquery using values from the outer query<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n-- Using subquery and CROSS APPLY\r\nSELECT CONVERT(VARCHAR,(h.RateChangeDate),103) AS DateFrom\r\n\t\t, CONCAT(LastName, ', ', FirstName, ' ', MiddleName) AS FullName \r\n\t\t, (40 * h.Rate) AS WeeklySalary\r\n\tFROM Person.Person AS p\r\n\t\tINNER JOIN HumanResources.EmployeePayHistory AS h\r\n\t\t\tON h.BusinessEntityID = p.BusinessEntityID\r\n\t\tCROSS APPLY\t(SELECT BusinessEntityID, MAX(RateChangeDate) AS RateChangeDate \r\n\t\t\t\t\t\tFROM HumanResources.EmployeePayHistory\r\n\t\t\t\t\t\tWHERE BusinessEntityID = h.BusinessEntityID\t\t\t\t\t\t\t\r\n\t\t\t\t\t\tGROUP BY BusinessEntityID\r\n\t\t\t\t\t\tHAVING MAX(RateChangeDate) = h.RateChangeDate) AS Cur\r\n\tORDER BY FullName\r\n\r\n<\/pre>\n<p>When queries get too big, sometimes for readability is better to split the logic in small parts to we can understand them better.<br \/>\nUsing <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms190766.aspx\" target=\"_blank\">Common Table Expressions<\/a> is a good way of doing it. In this case we calculate the MAX date for each employee beforehand and then use the CTE like a regular table to JOIN it to the other tables.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n-- Using a Common table expression (to make the query below smaller and more readable)\r\n-- Then can be combined as INNER JOIN or CROSS APPLY \r\n;WITH cte AS (\r\nSELECT BusinessEntityID, MAX(RateChangeDate) AS RateChangeDate \r\n\tFROM HumanResources.EmployeePayHistory\r\n\tGROUP BY BusinessEntityID \r\n)\r\n\r\nSELECT CONVERT(VARCHAR,(h.RateChangeDate),103) AS DateFrom\r\n\t\t, CONCAT(LastName, ', ', FirstName, ' ', MiddleName) AS FullName \r\n\t\t, (40 * h.Rate) AS WeeklySalary\r\n\tFROM Person.Person AS p\r\n\t\tINNER JOIN HumanResources.EmployeePayHistory AS h\r\n\t\t\tON h.BusinessEntityID = p.BusinessEntityID\r\n\t\tINNER JOIN cte AS Cur\r\n\t\t\tON cur.BusinessEntityID = h.BusinessEntityID \r\n\t\t\t\tAND cur.RateChangeDate = h.RateChangeDate\r\n\tORDER BY FullName\r\n<\/pre>\n<p>&nbsp;<br \/>\nAnd that was all for this first day of training. There are a lot of concepts involved here, so further reading is well recommended.<\/p>\n<ul>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms189499.aspx\" target=\"_blank\">SELECT<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms177634.aspx\" target=\"_blank\">FROM<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188047.aspx\" target=\"_blank\">WHERE<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188385.aspx\" target=\"_blank\">ORDER BY<\/a><\/li>\n<li><a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms190014.aspx\" target=\"_blank\">INNER JOIN<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms177673.aspx\" target=\"_blank\">GROUP BY<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms180199.aspx\" target=\"_blank\">HAVING<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/ms187751.aspx\" target=\"_blank\">MAX<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-gb\/library\/ms184325.aspx\" target=\"_blank\">ISNULL<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/hh231515.aspx\" target=\"_blank\">CONCAT<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/ms187928.aspx\" target=\"_blank\">CONVERT<\/a><\/li>\n<li><a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms187638.aspx\" target=\"_blank\">Correlated Subquerys<\/a><\/li>\n<li><a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms175156.aspx\" target=\"_blank\">CROSS APPLY<\/a><\/li>\n<li><a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms190766.aspx\" target=\"_blank\">Common Table Expressions<\/a><\/li>\n<\/ul>\n<p>&nbsp;<br \/>\nPlease feel free to throw me your questions and other solutions you came up with and I&#8217;ll try to get back to you soon.<\/p>\n<p>See you for Day 2 @ AdventureWorks!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Solutions for Day 1 of exercises using the AdventureWorks sample database&nbsp;It&#8217;s been a while since I published Day 1 questions and I guess it&#8217;s time to post the answers I came up&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[7,6,20,5],"_links":{"self":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/91"}],"collection":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/comments?post=91"}],"version-history":[{"count":0,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/91\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/media?parent=91"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/categories?post=91"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/tags?post=91"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}