How do I prevent the Order_ID's from repeating? - sql-server-2008

When I run this query, I get duplicate lines. Specifically, the order_ID is repeated for every possible ship_state related to that Customer_ID. If I remove the cust_address table from the query, I get the correct number of lines. How can I get just the Ship_states related to that particular order. Thanks.
SELECT
co.ID AS order_ID,
col.PART_ID,
col.ORDER_QTY,
co.STATUS,
co.SHIPTO_ID,
co.CUSTOMER_PO_REF,
co.CUSTOMER_ID,
c.STATE AS Bill_State,
ca.STATE AS Ship_State
FROM
dbo.CUSTOMER_ORDER AS co
INNER JOIN
dbo.CUST_ORDER_LINE AS col ON co.ID = col.CUST_ORDER_ID
INNER JOIN
dbo.CUSTOMER AS c ON co.CUSTOMER_ID = c.ID
INNER JOIN
dbo.CUST_ADDRESS AS ca ON c.ID = ca.CUSTOMER_ID
WHERE
(co.ORDER_DATE > '2014-01-01') AND (co.ID NOT LIKE 'rma%')
ORDER BY order_ID

This gave me unique order lines and order numbers for each shipping address. The next challenge is to figure out a way to populate the rows that have the same shipping and billing address. For these orders the shipping fields are null and information from the customer table is used instead.
SELECT ca.STATE AS ship_state,
co.ID,
co.CUSTOMER_ID,
ca.ADDR_NO,
co.SHIP_TO_ADDR_NO,
c.STATE AS Bill_state,
c.NAME AS Bill_name,
ca.NAME AS Ship_name
FROM
dbo.CUST_ORDER_LINE AS col
FULL OUTER JOIN
dbo.CUSTOMER_ORDER AS co ON col.CUST_ORDER_ID = co.ID
FULL OUTER JOIN
dbo.CUST_ADDRESS AS ca
FULL OUTER JOIN
dbo.CUSTOMER AS c ON ca.CUSTOMER_ID = c.ID
ON
co.CUSTOMER_ID = c.ID
AND
co.CUSTOMER_ID = ca.CUSTOMER_ID
AND
co.SHIP_TO_ADDR_NO = ca.ADDR_NO
WHERE
(co.ORDER_DATE > '2014-1-1') AND (co.ID NOT LIKE 'rma%')
ORDER BY co.ID

Related

Display results which have no count/zero as well

I am trying to get a count of the number of logins during a given timeframe, currently my SQL query displays only results that had at least one login, I'd like it to display even those which have zero logins.
Query i'm using:
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l JOIN UsersTable u ON u.Email = l.Email JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
You have a few issues. Firstly, you either need to use a RIGHT JOIN from LoginsTable or reorder the JOINs to put the JOIN to LoginsTable last and use a LEFT JOIN. Given the nature of your query the latter probably makes more sense.
Secondly, you need to put any conditions on fields from a table which has been LEFT JOINed into the join condition, otherwise MySQL converts the LEFT JOIN into an INNER JOIN (see the manual). Finally, you should GROUP BY the same fields as specified in your SELECT. This should work:
SELECT c.FullName, COUNT(l.Id)
FROM Organisations c
JOIN UsersTable u ON u.OrganisationId = c.Id
LEFT JOIN LoginsTable l ON u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName
I found 2 issues here:
your group by column is not listed on your column
date condition is using double quotes.
try below query.
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l
LEFT JOIN UsersTable u ON u.Email = l.Email
LEFT JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime between '2019-10-01' AND '2019-11-01' AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName ASC;
As Roman Hocke said you need to use left join as below :
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON u.Email = l.Email
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
Moreover, you should fix your group by or select using the same field : SELECT c.Name or GROUP BY c.FullName ORDER BY c.FullName
EDIT : Nick's answer is the one. As he said perfectly well, you need to put your conditions in the on clause of your left join.
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON (u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1)
GROUP BY c.FullName
ORDER BY c.FullName ASC;

use 2 left join can't work but separately can get results

I have three tables, company, user and share. I want to count one company's user and share, they are not relevant.
There may be a row that has share value but not user value. so I used left join, I can get results separately, but it doesn't work together.
Here is my query:
SELECT c.name, count(u.company_id), count(s.company_id)
FROM company c
LEFT JOIN user u
ON c.id=u.company_id and u.company_id=337
WHERE u.company_id is NOT NULL
LEFT JOIN share s
ON c.id=s.id AND s.company_id=337
WHERE s.company_id is NOT NULL
You need to do at least one of the counts in a subquery. Otherwise, both counts will be the same, since you're just counting the rows in the resulting cross product.
SELECT c.name, user_count, share_count
FROM company AS c
JOIN (SELECT company_id, COUNT(*) AS user_count
FROM users
GROUP BY company_id) AS u
ON u.company_id = c.id
JOIN (SELECT company_id, COUNT(*) AS share_count
FROM share
GROUP BY company_id) AS s
ON s.company_id = c.id
WHERE c.company_id = 337
Another option is to count the distinct primary keys of the tables you're joining with:
SELECT c.name, COUNT(DISTINCT u.id) AS user_count, COUNT(DISTINCT s.id) AS share_count
FROM company AS c
JOIN users AS u on u.company_id = c.id
JOIN share AS s ON s.company_id = c.id
WHERE c.company_id = 337
Your code looks okay, except for the extra WHERE clause. However, you probably want COUNT(DISTINCT), because the two counts will return the same value:
SELECT c.name, count(distinct u.company_id), count(distinct s.company_id)
FROM company c LEFT JOIN
user u
ON c.id = u.company_id and u.company_id=337 LEFT JOIN
share s
ON c.id = s.id AND s.company_id=337
WHERE s.company_id is NOT NULL AND u.company_id IS NOT NULL;

MySQL correlated subquery at FROM

I'm working with the Sakila sample database, and trying to get the most viewed film per country. So far I've managed to get the most viewed film of a certain country given its id with the following query:
SELECT
F.title, CO.country, count(F.film_id) as times
FROM
customer C
INNER JOIN
address A ON C.address_id = A.address_id
INNER JOIN
city CI ON A.city_id = CI.city_id
INNER JOIN
country CO ON CI.country_id = CO.country_id
INNER JOIN
rental R ON C.customer_id = R.customer_id
INNER JOIN
inventory I ON R.inventory_id = I.inventory_id
INNER JOIN
film F ON I.film_id = F.film_id
WHERE
CO.country_id = 1
GROUP BY
F.film_id
ORDER BY
times DESC
LIMIT 1;
I supose that I'll have to use this query or something similar in the FORM of another query, but I've tried it all I could think and am completely unable to figure out how to do so.
Thanks in advance!
I admit, this is a hell of a query. But well, as long as it works.
Explanation:
Subquery: almost the same as you already has. Without the WHERE and LIMIT. Resulting in a list of movie-count per country
Result of that, grouped per country
GROUP_CONCAT(title ORDER BY times DESC SEPARATOR '|||'), will give ALL titles in that 'row', with the most-viewed title first. The separator doesn't matter, as long as you are sure it will never occurs in a title.
SUBSTRING_INDEX('...', '|||', 1) results in the first part of the string until it finds |||, in this case the first (and thus most-viewed) title
Full query:
SELECT
country_name,
SUBSTRING_INDEX(
GROUP_CONCAT(title ORDER BY times DESC SEPARATOR '|||'),
'|||', 1
) as title,
MAX(times)
FROM (
SELECT
F.title AS title,
CO.country_id AS country_id,
CO.country AS country_name,
count(F.film_id) as times
FROM customer C INNER JOIN address A ON C.address_id = A.address_id
INNER JOIN city CI ON A.city_id = CI.city_id
INNER JOIN country CO ON CI.country_id = CO.country_id
INNER JOIN rental R ON C.customer_id = R.customer_id
INNER JOIN inventory I ON R.inventory_id = I.inventory_id
INNER JOIN film F ON I.film_id = F.film_id
GROUP BY F.film_id, CO.country_id
) AS count_per_movie_per_country
GROUP BY country_id
Proof of concept (as long as the subquery is correct): SQLFiddle

Is this the right way to join tables to fetch data?

I have a database with the tables:
Student(SID,Name,Surname,Age)
Registration(StudentID,CourseID)
Course(CID,Name,Cost)
I would like to extract only the name of the courses with students younger than 20. Will the query below do just that?
SELECT C.NAME
FROM Course C
INNER JOIN Registration
INNER JOIN Student S
WHERE CID = CourseID
AND SID = StudentID
AND Age < 20
GROUP BY C.NAME
I would also like to extract the number of students in each course having students younger than 20. Is it correct to do it as below?
SELECT count(S.NAME)
,C.NAME
FROM Student S
INNER JOIN Course C
INNER JOIN Registration
WHERE Age < 20
AND CID = CourseID
AND SID = StudentID
GROUP BY C.NAME
You are missing the ON part for the join otherwise it would just be a CROSS JOIN.
Your first query should look like this if you want just a distinct list of student names:
SELECT DISTINCT C.NAME
FROM Course C
INNER JOIN Registration R ON C.CID = R.CourseID
INNER JOIN Student S ON R.StudentID = S.SID
WHERE Age < 20
Your second query shouldn't really have the C.Name in the select if you want to get just a count unless you want a count of how many students have that name.
SELECT count(*)
FROM Student S
INNER JOIN Registration R ON s.SID = R.StudentID
INNER JOIN Course C ON c.CID = R.CourseID
WHERE Age < 20
GROUP BY C.NAME
First join these tables, then group by Course's PK(CID), Add the HAVING condition to filter the course which has students younger than 20.
Then use Course table to join the result to get the course name and count of students in the course.
SELECT
T1.Name,
T2.StudentCount
FROM
Course T1
INNER JOIN (
SELECT
c.CID,
COUNT(s.SID) AS StudentCount
FROM
Course c
LEFT JOIN Registration r ON c.CID = r.CourseID
LEFT JOIN Student s ON s.SID = r.StudentID
GROUP BY c.CID
HAVING COUNT(IF(s.Age < 20, 1, NULL)) > 0
) T2 ON T1.CID = T2.CID
More correctly, you should move the conditions of the join, to the join statements themselves by including them in the on clause instead of the where. While the results may not change in this instance, if you were to start including outer joins you would encounter difficulties.
SELECT count(S.NAME)
,C.NAME
FROM Student S
INNER JOIN Registration R
ON s.SID = R.StudentID
INNER JOIN Course C
ON c.CID = R.CourseID
WHERE Age < 20
GROUP BY C.NAME
There's a fiddle here showing it in action: http://sqlfiddle.com/#!9/c3b8f/1
Your first query will also produce the results you want, but again, you should move the join predicates to the join itself. Also, you don't need to perform the grouping just to get distinct values, mysql has an expression for that called distinct. So rewritten, the first query would look like:
SELECT DISTINCT C.NAME
FROM Student S
INNER JOIN Registration R
ON s.SID = R.StudentID
INNER JOIN Course C
ON c.CID = R.CourseID
WHERE Age < 20.
Again, the results are the same as what you have already but it is easier to 'read' and will put you in good stead when you move on to other queries. As it stands you have mixed implicit and explicit join syntax.
This fiddle demonstrates both queries: http://sqlfiddle.com/#!9/c3b8f/4
edit
I may have misinterpreted your original question - if you want the total number of students enrolled in a course with at least one student under 19, you can use a query like this:
select name, count(*)
from course c
inner join registration r
on c.cid = r.courseid
where exists (
select 1
from course cc
inner join registration r
on cc.cid = r.courseid
inner join student s
on s.sid = r.studentid
where cc.cid = c.cid
group by cc.cid
having min(s.age) < 20
)
group by name;
Again with the updated fiddle here: http://sqlfiddle.com/#!9/c3b8f/17

How to replace null data with information from another table?

Order_ID ship_state bill_state
121 null CA
122 WA IN
123 CA OR
124 null FL
I have a query (below) that brings back these results (above). Order_ID, Ship_state, and bill_state are each in different tables. How can I modify my query so that it will populate the null ship_state with the corresponding bill_state? Some orders are shipped directly to the billing address, this makes the shipping fields null as they are left blank. I want to generate a list of ship to states.
SELECT ca.STATE AS ship_state,
co.ID,
co.CUSTOMER_ID,
ca.ADDR_NO,
co.SHIP_TO_ADDR_NO,
c.STATE AS Bill_state,
c.NAME AS Bill_name,
ca.NAME AS Ship_name,
col.PART_ID,
col.ORDER_QTY,
ca.COUNTRY AS Ship_Country,
c.COUNTRY AS Bill_country,
co.ORDER_DATE
FROM dbo.CUST_ORDER_LINE AS col
FULL OUTER JOIN dbo.CUST_ADDRESS AS ca
FULL OUTER JOIN dbo.CUSTOMER AS c
INNER JOIN dbo.CUSTOMER_ORDER AS co
ON c.ID = co.CUSTOMER_ID
ON ca.CUSTOMER_ID = co.CUSTOMER_ID
AND ca.ADDR_NO = co.SHIP_TO_ADDR_NO
AND ca.CUSTOMER_ID = c.ID
ON col.CUST_ORDER_ID = co.ID
WHERE co.ORDER_DATE > '2014-1-1'
AND co.ID NOT LIKE 'rma%'
ORDER BY co.ID
You can use the COALESCE function:
In your SELECT:
COALESCE(ca.STATE, c.STATE) AS ship_state
This will return the value of c.STATE if ca.STATE is NULL.
Use ISNULL function,
ISNULL(ca.State, c.State) As ship_state