Think up algorithm: Calculate full earnings for every company - sql

I have a TreeView with companies. Every company can have subcompanies, every subcompany can have subcompanies and this can continue to infinity.
Every company have own earnings, and have FullEarnings which equals sum of all subcompanies this company.
I need to calculate earnings for every company if known own earnings every company.
Example:
CompanyName | OwnEarnings | FullEarnings
-Company1 | 25K$ | 53K$
--Company2 | 13K$ | 18K$
---Company3 | 5K$
--Company4 | 10K$
I have a database column ParentID, which link to id parent company.
How i can do it? Maybe by recursion?

One way or another, you will need to recursively update your table.
In your case that will look something like:
with C as
(
select T.Id,
T.Earnings,
T.Id as RootID
from T
union all
select T.Id,
T.Earnings,
C.RootID
from T
inner join C
on T.ParentId = C.Id
)
select T.Id,
T.ParentId,
T.CompanyName,
T.Earnings,
S.FullEarnings
from T
inner join (
select RootID,
sum(Earnings) as FullEarnings
from C
group by RootID
) as S
on T.Id = S.RootID
order by T.Id
option (maxrecursion 0);
SQL Fiddle example
In order to update, you will want to change out the select with an update query as shown in this SQL Fiddle example

Related

How can I join multiple tables on the same auto generated integer from one "table"?

I want to create a random selected cross-joined table which auto increments its own id and joins on it.
Let's say my tables looking like this.
Person
Firstname, Lastname
Hans | Müller
Joachim | Bugert
Address
City, Street, StreetNumber
Hamburg | Wandsbeckerstr. | 2
Berlin | Konradstraße | 13
Now I want to join the tables with a auto generated ID and they should be random selected.
The final table should look like this
ID,Firstname,Lastname, City, Street, StreetNumber
1 |Hans|Bugert|Berlin|Wandsbeckerstr|2
2|Joachim|Müller|Hamburg|Konradstraße | 13
What I already tried or used:
Here I auto-generate the ID where I want to join the tables on
select GENERATED_PERIOD_START as ID FROM SERIES_GENERATE_INTEGER(1,1,10)
The problem is cross join and inner join isn't working for me because it always joins everything with everything or its not joining on the same ID.
SELECT Person."Firstname", Person."Lastname", Address."City",Address."Street", Address."StreetNumber"
FROM
( select GENERATED_PERIOD_START as ID FROM SERIES_GENERATE_INTEGER(1,1,10)
) autoGenID
inner JOIN
(select "Firstname" ,"Lastname" FROM Person ORDER BY RAND()) Person
inner JOIN
(select "City", "Street", "StreetNumber", FROM Address ORDER BY RAND()) Address
JOIN ON autoGenID."ID"=?????
Here is my problem I can't just select random data and select that on my auto generated ID.
Thanks for your help or ideas how to solve this!
I think you want:
SELECT p."Firstname", p."Lastname", a."City", a."Street", a."StreetNumber"
FROM (SELECT p.*,
ROW_NUMBER() OVER (ORDER BY RAND()) as seqnum
FROM Person p
) p JOIN
(SELECT a.*,
ROW_NUMBER() OVER (ORDER BY RAND()) as seqnum
FROM Address a
) a
ON p.seqnum = a.seqnum;

SQL Removing Duplicate rows

I've been trying to remove duplicates using HAVING count(*) > 1, group by, distinct and sub queries but can't get any of these to work..
SELECT UserID, BuildingNo
FROM Staff INNER JOIN TblBuildings ON Staff.StaffID =
TblBuildingsStaffID
GROUP BY TblStaff.User_Code, BuildingNo
What I get is..
StaffID1 | BuildingNo1
StaffID1 | BuildingNo2
StaffID2 | BuildingNo2
StaffID3 | BuildingNo1
StaffID3 | BuildingNo2
I'm trying to get it so it just displays staff with one building number (if they have two regardless of which it shows) like:
StaffID1 | BuildingNo1
StaffID2 | BuildingNo2
StaffID3 | BuildingNo1
It can't be too hard.. I've tried CTE's left joining the building to the staff table, these come up NULL for some reason when I try this
Any help would be great!
Don't group by BuildingNo, then you can use having to filter out the groups you want.
SELECT s.UserID, min(b.BuildingNo) as buildingno
FROM Staff s
JOIN TblBuildings ON s.StaffID = b.TblBuildingsStaffID
GROUP BY s.UserID
having count(distinct b.BuildingNo) = 1;
The min() aggregate is required because buildingno is not part of the group by clause. But as the having() clause only returns those with one building, it doesn't change anything.
If you want to display all staff members, and simply pick one (arbitrary) building, then simply leave out the having condition.
If you want to include staff members without a building you need a left join:
SELECT s.UserID, min(b.BuildingNo) as buildingno
FROM Staff s
LEFT JOIN TblBuildings b ON s.StaffID = t.TblBuildingsStaffID
GROUP BY t.UserID;
Use row partition keyword in your query to avoid duplicacy
WITH CTE AS( SELECT ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY UserID ) AS 'Num',UserID, BuildingNo
FROM Staff INNER JOIN TblBuildings ON Staff.StaffID =
TblBuildingsStaffID
GROUP BY TblStaff.User_Code, BuildingNo)
SELECT * FROM CTE
WHERE Num =1
try this -
SELECT distinct UserID, BuildingNo
FROM Staff INNER JOIN TblBuildings ON Staff.StaffID =
TblBuildingsStaffID

Select all customers loyal to one company?

I've got tables:
TABLE | COLUMNS
----------+----------------------------------
CUSTOMER | C_ID, C_NAME, C_ADDRESS
SHOP | S_ID, S_NAME, S_ADDRESS, S_COMPANY
ORDER | S_ID, C_ID, O_DATE
I want to select id of all customers who made order only from shops of one company - 'Samsung' ('LG', 'HP', ... doesn't really matter, it's dynamic).
I've come only with one solution, but I consider it ugly:
( SELECT DISTINCT c_id FROM order JOIN shop USING(s_id) WHERE s_company = "Samsung" )
EXCEPT
( SELECT DISTINCT c_id FROM order JOIN shop USING(s_id) WHERE s_company != "Samsung" );
Same SQL queries, but reversed operator. Isn't there any aggregate method which solves such query better?
I mean, there could be millions of orders(I don't really have orders, I've got something that occurs more often).
Is it efficient to select thousands of orders and then compare them to hundreds of thousands orders which have different company? I know, that it compares sorted things, so it's O( m + n + sort(n) + sort(m) ). But that's still large for millions of records, or isn't?
And one more question. How could I select all customer values (name, address). How can I join them, can I do just
SELECT CUSTOMER.* FROM CUSTOMER JOIN ( (SELECT...) EXCEPT (SELECT...) ) USING (C_ID);
Disclaimer: This question ain't homework. It's preparation for the exam and desire to things more effective. My solution would be accepted at exam, but I like effective programming.
I like to approach this type of question using group by and a having clause. You can get the list of customers using:
select o.c_id
from orders o join
shops s
on o.s_id = o.s_id
group by c_id
having min(s.s_company) = max(s.s_company);
If you care about the particular company, then:
having min(s.s_company) = max(s.s_company) and
max(s.s_company) = 'Samsung'
If you want full customer information, you can join the customers table back in.
Whether this works better than the except version is something that would have to be tested on your system.
How about a query that uses no aggregate functions like Min and Max?
select C_ID, S_ID
from shop
group by C_ID, S_ID;
Now we have a distinct list of customers and all the companies they shopped at. The loyal customers will be the ones who only appear once in the list.
select C_ID
from Q1
group by C_ID
having count(*) = 1;
Join back to the first query to get the company id:
with
Q1 as(
select C_ID, S_ID
from shop
group by C_ID, S_ID
),
Q2 as(
select C_ID
from Q1
group by C_ID
having count(*) = 1
)
select Q1.C_ID, Q1.S_ID
from Q1
join Q2
on Q2.C_ID = Q1.C_ID;
Now you have a list of loyal customers and the one company each is loyal to.

SQL display two results side-by-side

I have two tables, and am doing an ordered select on each of them. I wold like to see the results of both orders in one result.
Example (simplified):
"SELECT * FROM table1 ORDER BY visits;"
name|# of visits
----+-----------
AA | 5
BB | 9
CC | 12
.
.
.
"SELECT * FROM table2 ORDER BY spent;"
name|$ spent
----+-------
AA | 20
CC | 30
BB | 50
.
.
.
I want to display the results as two columns so I can visually get a feeling if the most frequent visitors are also the best buyers. (I know this example is bad DB design and not a real scenario. It is an example)
I want to get this:
name by visits|name by spent
--------------+-------------
AA | AA
BB | CC
CC | BB
I am using SQLite.
Select A.Name as NameByVisits, B.Name as NameBySpent
From (Select C.*, RowId as RowNumber From (Select Name From Table1 Order by visits) C) A
Inner Join
(Select D.*, RowId as RowNumber From (Select Name From Table2 Order by spent) D) B
On A.RowNumber = B.RowNumber
Try this
select
ISNULL(ts.rn,tv.rn),
spent.name,
visits.name
from
(select *, (select count(*) rn from spent s where s.value>=spent.value ) rn from spent) ts
full outer join
(select *, (select count(*) rn from visits v where v.visits>=visits.visits ) rn from visits) tv
on ts.rn = tv.rn
order by ISNULL(ts.rn,tv.rn)
It creates a rank for each entry in the source table, and joins the two on their rank. If there are duplicate ranks they will return duplicates in the results.
I know it is not a direct answer, but I was searching for it so in case someone needs it: this is a simpler solution for when the results are only one per column:
select
(select roleid from role where rolename='app.roles/anon') roleid, -- the name of the subselect will be the name of the column
(select userid from users where username='pepe') userid; -- same here
Result:
roleid | userid
--------------------------------------+--------------------------------------
31aa33c4-4e66-4da3-8525-42689e46e635 | 12ad8c95-fbef-4287-9834-7458a4b250ee
For RDBMS that support common table expressions and window functions (e.g., SQL Server, Oracle, PostreSQL), I would use:
WITH most_visited AS
(
SELECT ROW_NUMBER() OVER (ORDER BY num_visits) AS num, name, num_visits
FROM visits
),
most_spent AS
(
SELECT ROW_NUMBER() OVER (ORDER BY amt_spent) AS num, name, amt_spent
FROM spent
)
SELECT mv.name, ms.name
FROM most_visited mv INNER JOIN most_spent ms
ON mv.num = ms.num
ORDER BY mv.num
Just join table1 and table2 with name as key like bellow:
select a.name,
b.name,
a.NumOfVisitField,
b.TotalSpentField
from table1 a
left join table2 b on a.name = b.name

Limiting Results on Join Query

Say I have the following tables:
|RefNumber| Charge| IssueDate|
------------------------------
| 00001| 40.0|2009-01-01|
| 00002| 40.0|2009-06-21|
|ID|RefNumber|Forename| Surname|
---------------------------------
1| 00001| Joe| Blogs|
2| 00001| David| Jones|
3| 00002| John| Smith|
4| 00002| Paul| Walsh|
I would like to select refnumber, charge and issuedate from the first table then join on refnumber to the second table to retrieve forename and surname but only get the row with the highest id.
So results would look like:
|RefNumber| Charge| IssueDate|ID|Forename| Surname|
-----------------------------------------------------
| 00001| 40.0|2009-01-01| 2| David| Jones|
| 00002| 40.0|2009-06-21| 4| Paul| Walsh|
I am unsure who to limit results on the join to only return the record with the highest ID from the second table.
The most flexible way to write this, which doesn't require a correlated subquery, is to use ROW_NUMBER (SQL Server 2005+ only):
;WITH Names_CTE AS
(
SELECT
ID, RefNumber, Forename, Surname,
ROW_NUMBER() OVER (PARTITION BY RefNumber ORDER BY ID) AS RowNum
FROM Names
)
SELECT o.RefNumber, o.Charge, o.IssueDate, n.Forename, n.Surname
FROM Orders o
[INNER|LEFT] JOIN Names_CTE n
ON n.RefNumber = o.RefNumber
WHERE n.RowNum = 1
Note that ROW_NUMBER isn't always the most efficient if you can use MIN/MAX instead, just the easiest to write.
If you're running SQL 2000, or this isn't efficient enough, you can try a MIN or MAX query:
SELECT o.RefNumber, o.Charge, o.IssueDate, n.Forename, n.Surname
FROM Orders o
[INNER|LEFT] JOIN
(
SELECT RefNumber, MIN(ID) AS MinID
FROM Names
GROUP BY RefNumber
) m
ON m.RefNumber = o.RefNumber
[INNER|LEFT] JOIN Names n
ON n.ID = m.MinID
Sometimes this is actually faster, it depends a lot on the indexing strategy used.
(Edit - this gets rows with the lowest ID, which in most cases will be faster than getting the highest ID. If you need the highest, change the first query to ORDER BY ID DESC and the second query to use MAX instead of MIN).
I'd probably join against a subquery that returns only record from the second table with the highest id.
select a.RefNumber, a.Charge, a.IssueDate, b.ID, b.Forename, b.Surname
from References a inner join
(select ID, RefNumber, ForeName, Surname from Names n1
where n1.ID = (select top 1 n2.ID from Names n2 where n1.RefNumber = n2.RefNumber) ) b
on a.RefNumber = b.RefNumber
Actually your subquery would need to select the highest ID for EACH refnumber, so that would look more like this:
select
a.RefNumber, a.Charge, a.IssueDate, b.BiggestID, b.Forename, b.Surname
from References a
inner join
(select
RefNumber,
max(ID) as BiggestID
from Names
group by
RefNumber) b
on a.RefNumber = b.RefNumber
Hope that helps.
-Tom
select nt.RefNumber, ct.Charge, ct.IssueDate, nt.ID, nt.Forename, nt.Surname
from NameTable nt
join ChargeTable ct on (ct.RefNumber = nt.RefNumber)
where nt.ID = (select MAX(nt2.id)
from NameTable nt2
where nt2.RefNumber = nt.RefNumber)
order by nt.ID