Can i do a inner "With" inside another "With" in SQL? - sql

I'm trying to use multiple SQL With clauses.
The reason of me using multiple With is that I'm sending this SQL to a AS400 project. The With TEMP has to be obligatory instead of Temp2 that has to be optional.
I can't figure out how to do it. This SQL still throws an error:
With Temp2 As
(
With Temp As
(
Select Name, Surname, Age
From People
Where Age > 18
)
Select A.*, B.*
From Temp A
Left Join City B on B.Name = A.Name
and B.Surname = A.Surname
Where B.City = "Venice"
)
Select *
From Temp2 C
Left Join State D on D.City = C.City
I'd like to understand how I can do something like that.

Yes, any CTE can reference a CTE that is created before it. The first CTE must be prefaced by "With" and terminated with a comma, which allows for another CTE to be created.
with temp as
(
select name, surname, age
from people
where age > 18
),
temp2 as
(
select a.*, b.*
from temp a
left join city b
on b.name = a.name
and b.surname = a.surname
where b.city = "Venice"
)
select *
from temp2 c
left join state d
on d.city = c.city
;
This is functionally equivalent to the query below, which does not require any CTE's.
select *
from people as a
join city b
on b.name = a.name
and b.surname = a.surname
and b.city = "Venice"
left join state c
on c.city = b.city
where a.age > 18
;

For what you are describing, you shouldn't need either CTEs or subqueries. Just use regular JOINs.
SELECT p.Name, p.Surname, p.Age, C.City, s.StateName, s.CountryName
FROM People p
INNER JOIN City c ON p.Name = c.Name
AND p.Surname = c.Surname
AND c.City = 'Venice'
LEFT OUTER JOIN State s ON c.City = s.City
WHERE p.Age > 18
See https://dbfiddle.uk/?rdbms=db2_11.1&fiddle=6d16c8325ee1da354588ddddc75bb162 for demo.

Related

How to combine multiple rows into one ,getting the one column that differs as comma separated values in t-sql?

I want to combine multiple rows into one, getting the one column that differs as comma-separated values.
I have written the below query and it gives the result as shown below.
I want 4 rows instead of 9, the last column should appear comma separated like (Storage, Wastewater, Misc).
Please help with your ideas, Thanks in advance!
SELECT DISTINCT
C.CONTRACTID, C.NUMBER, C.STATE,
O.CUSTOMERCODE, O.CUSTOMERNAME,
C.STARTDATE, C.TERMINATIONDATE, CT.Name AS CONTRACTTYPELIST
FROM
[DBO].[CONTRACT] C
INNER JOIN
[ORD].[ORDER] O ON C.CUSTOMERID = O.CUSTOMERID
INNER JOIN
[dbo].[Contract_ContractType] CCT ON CCT.ContractId = C.ContractId
INNER JOIN
[Ref].[ContractType] CT ON CT.ContractTypeId = CCT.ContractTypeId
WHERE
O.ORDERSTATEID = 6
ORDER BY
c.ContractId
I updated the query like below , but it gives long string in the last column but i want only values for that particular record id in one row. How can this be corrected ?
SELECT distinct
C.CONTRACTID,C.NUMBER, C.STATE ,
O.CUSTOMERCODE,O.CUSTOMERNAME ,
C.STARTDATE , C.TERMINATIONDATE ,
STRING_AGG(CAST(CT.Name AS NVARCHAR(MAX)) , ',') AS CONTRACTTYPELIST
FROM
[DBO].[CONTRACT] C
INNER JOIN
[ORDERING].[ORDER] O ON C.CUSTOMERID = O.CUSTOMERID
INNER JOIN
[dbo].[Contract_ContractType] CCT on CCT.ContractId = C.ContractId
INNER JOIN
[Ref].[ContractType] CT on CT.ContractTypeId = CCT.ContractTypeId
WHERE
O.ORDERSTATEID = 6
GROUP BY
C.CONTRACTID,C.NUMBER, C.STATE ,
O.CUSTOMERCODE,O.CUSTOMERNAME ,
C.STARTDATE , C.TERMINATIONDATE
You want to use a GROUP BY clause together with the STRING_AGG function
Example:
SELECT STRING_AGG(column_D, ',')
FROM dbo.table
GROUP BY column_A, column_B, column_C
Wrap the query with the DISTINCT in a sub-query.
Then use STRING_AGG in the outer query.
SELECT
CONTRACTID, [NUMBER], STATE,
CUSTOMERCODE, CUSTOMERNAME,
STARTDATE, TERMINATIONDATE,
STRING_AGG(CONTRACTTYPE,',') AS CONTRACTTYPELIST
FROM
(
SELECT DISTINCT
C.CONTRACTID, C.NUMBER, C.STATE,
O.CUSTOMERCODE, O.CUSTOMERNAME,
C.STARTDATE, C.TERMINATIONDATE,
CT.Name AS CONTRACTTYPE
FROM
[DBO].[CONTRACT] C
JOIN [ORDERING].[ORDER] O
ON C.CUSTOMERID = O.CUSTOMERID
JOIN [dbo].[Contract_ContractType] CCT
ON CCT.ContractId = C.ContractId
JOIN [Ref].[ContractType] CT
ON CT.ContractTypeId = CCT.ContractTypeId
WHERE
O.ORDERSTATEID = 6
) q
GROUP BY
CONTRACTID, [NUMBER], STATE,
CUSTOMERCODE, CUSTOMERNAME,
STARTDATE, TERMINATIONDATE

SQL - Sum of query not true

I have this query;
SELECT l.Name, COALESCE(SUM(A.Count), 0) AS A, COALESCE(SUM(B.Count), 0) AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name
ORDER BY l.Name
And query results not true.
Sum of Product3 in Table A is not true.
Demo : https://www.db-fiddle.com/f/rdKLkyaeEsi8bPcNPkUnTE/4
You could sum separately for A and B and then combine results:
SELECT Name, MAX(A) AS A, MAX(B) AS B
FROM (
SELECT l.Name, SUM(A.Count) AS A, 0 AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
GROUP BY l.Name
UNION ALL
SELECT l.Name, 0 AS A, SUM(B.Count)AS B
FROM List l
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name) sub
GROUP BY Name
ORDER BY Name;
db-fiddle.com demo
You should be aggregating the A and B tables in separate subqueries:
SELECT
l.Name,
COALESCE(a.cnt, 0) AS a_cnt,
COALESCE(b.cnt, 0) AS b_cnt
FROM List l
LEFT JOIN
(
SELECT Name, SUM(Count) AS cnt
FROM A
GROUP BY Name
) a
ON l.Name = a.name
LEFT JOIN
(
SELECT Name, SUM(Count) AS cnt
FROM B
GROUP BY Name
) b
ON l.Name = b.name;
The problem with your current approach is that the double join to the A and B tables is likely resulting in double counting. By using separate subqueries we avoid this problem.
In your original question on this subject, I suggested correlated subqueries. These are probably the easiest way to accomplish what you want:
select l.name,
(select sum(a.count)
from a
where a.name = l.name
) as a,
(select sum(b.count)
from b
where b.name = l.name
) as b
from list l;
You should check null values before sum() not after.
SELECT l.Name, SUM(COALESCE(A.Count, 0)) AS A, SUM(COALESCE(B.Count, 0)) AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name
ORDER BY l.Name

Senior Manager of Employee sql

I have an employee table:
I have a category table which joins userid and category id:
Category with value 1 is senior manager.
I want to find senior manager of each employee.Seniors Managers with category value of 1.
I need the output like this:
How can we achieve this in SQL Server 2008?
Any help appreciated.
Try:
WITH Emp_CTE AS (
Select ID,EmployeeName,Manager from employee
UNION ALL
SELECT ecte.ID,ecte.EmployeeName,c.Manager
FROM employee c
INNER JOIN Emp_CTE ecte ON ecte.Manager = c.ID
)
SELECT a.ID,a.EmployeeName,b.EmployeeName
FROM Emp_CTE a
left join employee b
on a.Manager = b.ID
left join category c
on a.Manager = c.UserID
where c.Category = '1'
As this query calls for a Self Join, this should work:
SELECT a.EMPLOYEENAME as Employee,
b.EMPLOYEENAME as Senior_Manager
FROM employee a
LEFT JOIN
employee b ON a.ID = b.Manager
LEFT JOIN
category c ON b.ID = c.UserID
WHERE c.Category = 1

SQL joining two tables and counting

I want to retrieve all the customers who own more than 1 car.
I have this code:
SELECT c.fname,
c.lname,
c.cid,
Count(v.cid) AS nmbrofvehicle
FROM customer c
LEFT JOIN vehicle v
ON c.cid = v.cid
GROUP BY c.fname;
But it returns this error:
ORA-00979: not a GROUP BY expression
select c.fname, c.lname, c.cid, count(v.cid) as nmbrofvehicle
from customer c left join vehicle v on c.cid = v.cid
group by c.fname, c.lname, c.cid
having count(*) > 1;
The problem you have there is that you are attempting to group in ungrouped data (e.g. surname).
Some databases (such as MySQL) are very forgiving and try to do this - Oracle, however is not.
Try this:
SELECT a.cid
, a.fname
, a.sname
, NVL(b.nmbrofvehicle, 0) AS nmbrofvehicle
FROM customers a
LEFT JOIN ( SELECT z.cid
, COUNT(z.cid) AS nmbrofvehicle
FROM vehicle z
GROUP BY z.cid
) b
ON ( a.cid = b.cid );
This will take data from customers, and left join any matching data from your vehicles table on cid, or return 0 thanks to this NVL function.
Remove c.cid from the select and add c.lname to the group by:
SELECT c.fname, c.lname,
Count(v.cid) AS nmbrofvehicle
FROM customer c JOIN
vehicle v
ON c.cid = v.cid
GROUP BY c.fname, c.lname
HAVING COUNT(*) > 1;
A LEFT JOIN is unnecessary because you are requiring at least one match.

Query returning too many results

SQL query that returns expected 29 results for a.id = 366
select a.name, c.name, MAX(B.date), MAX(b.renew_date) as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
where a.id = 366
GROUP BY a.name, c.name
order by MAX(b.renew_date), MAX(b.date) desc;
SQL code below that returns 34 results, multiple results where two different Provides supplied the same course. I know these extra results are because I added e.name to the list to be returned. But all that is needed is the 29 entries with the latest date and Providers names.
select a.name, c.name, e.name, MAX(B.date), MAX(b.renew_date) as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
inner join boson_provider e on b.provider_id = e.id
where a.id = 366
GROUP BY a.name, c.name, e.name
order by MAX(b.renew_date), MAX(b.date) desc;
Can anyone rework this code to return a single DISTINCT Provider name with the MAX(renew_date) for each course.
This returns exactly one row per distinct combination of (a.name, c.name):
The one with the latest renew_date.
Among these, the one with the latest date (may differ from global max(date)!).
Among these, the one with the alphabetically first e.name:
SELECT DISTINCT ON (a.name, c.name)
a.name AS a_name, c.name AS c_name, e.name AS e_name
, b.renew_date, b.date
FROM boson_course c
JOIN boson_coursedetail b on c.id = b.course_id
JOIN boson_coursedetail_attendance d on d.coursedetail_id = b.id
JOIN boson_employee a on a.id = d.employee_id
JOIN boson_provider e on b.provider_id = e.id
WHERE a.id = 366
ORDER BY a.name, c.name
, b.renew_date DESC NULLS LAST
, b.date DESC NULLS LAST
, e.name;
The result is sorted by a_name, c_name first. If you need your original sort order, wrap this in a subquery:
SELECT *
FROM (<query from above>) sub
ORDER BY renew_date DESC NULLS LAST
, date DESC NULLS LAST
, a_name, c_name, e_name;
Explanation for DISTINCT ON:
Select first row in each GROUP BY group?
Why DESC NULL LAST?
PostgreSQL sort by datetime asc, null first?
Aside: Don't use basic type names like date ad column names. Also, name is hardly ever a good name. As you can see, we have to use aliases to make this query useful. Some general advice on naming conventions:
How to implement a many-to-many relationship in PostgreSQL?
Try using distinct on:
select distinct on (a.name, c.name, e.name), a.name, c.name, e.name,
B.date, b.renew_date as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
inner join boson_provider e on b.provider_id = e.id
where a.id = 366
ORDER BY a.name, c.name, e.name, B.date desc
order by MAX(b.renew_date), MAX(b.date) desc;