Rownum order is incorrect after join - SQL Server - sql

http://sqlfiddle.com/#!18/97fbe/1 - fiddle
I have tried to demo my real life scenario as much as possible
Tables:
CREATE TABLE [OrderTable]
(
[id] int,
[OrderGroupID] int,
[Total] int,
[fkPerson] int,
[fkitem] int
PRIMARY KEY (id)
)
INSERT INTO [OrderTable] (id, OrderGroupID, Total ,[fkPerson], [fkItem])
VALUES
('1', '1', '20', '1', '1'),
('2', '1', '45', '2', '2'),
('3', '2', '32', '1', '1'),
('4', '2', '30', '2', '2'),
('5', '2', '32', '1', '1'),
('6', '2', '32', '3', '1'),
('7', '2', '32', '4', '1'),
('8', '2', '32', '4', '1'),
('9', '2', '32', '5', '1');
CREATE TABLE [Person]
(
[id] int,
[Name] varchar(32)
PRIMARY KEY (id)
)
INSERT INTO [Person] (id, Name)
VALUES
('1', 'Fred'),
('2', 'Sam'),
('3', 'Ryan'),
('4', 'Tim'),
('5', 'Gary');
CREATE TABLE [Item]
(
[id] int,
[ItemNo] varchar(32),
[Price] int
PRIMARY KEY (id)
)
INSERT INTO [Item] (id, ItemNo, Price)
VALUES
('1', '453', '23'),
('2', '657', '34');
Query:
WITH TABLE1 AS
(
SELECT
-- P.ID AS [PersonID],
-- P.Name,
SUM(OT.[Total]) AS [Total],
i.[id] AS [ItemID],
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rownum,
ot.fkperson,
[fkItem]
FROM
OrderTable OT
-- INNER JOIN Person P ON P.ID = OT.fkperson
INNER JOIN
Item I ON I.[id] = OT.[fkItem]
GROUP BY
-- P.ID, P.Name,
i.id, ot.fkperson, [fkItem]
)
SELECT
t1.[fkperson],
P.[Name],
t1.[itemid],
t1.[total],
t1.[rownum]
-- Totalrows = (SELECT MAX(rownum) FROM TABLE1)
FROM
TABLE1 T1
INNER JOIN
Person P ON P.ID = T1.fkperson
INNER JOIN
Item I ON I.[id] = T1.[fkItem]
Result:
| fkperson | Name | itemid | total | rownum |
+----------+------+--------+-------+--------+
| 1 | Fred | 1 | 84 | 1 |
| 3 | Ryan | 1 | 32 | 2 |
| 4 | Tim | 1 | 64 | 3 |
| 5 | Gary | 1 | 32 | 4 |
| 2 | Sam | 2 | 75 | 5 |
which is the result I want. However, my real-life example is giving me the row number in a weird order. I know its an issue with a join because when i comment these join:
INNER JOIN
Person P ON P.ID = T1.fkperson
INNER JOIN
Item I ON I.[id] = T1.[fkItem]
out it works fine.
| fkperson | Name | itemid | total | rownum |
|----------|------|--------|-------|--------|
| 1 | Fred | 1 | 84 | 4 |
| 3 | Ryan | 1 | 32 | 3 |
| 4 | Tim | 1 | 64 | 5 |
| 5 | Gary | 1 | 32 | 1 |
| 2 | Sam | 2 | 75 | 2 |
Has anyone got any advice on how the join would be causing these weird rownumber ordering? Or point me in the right direction. Thanks

Any relational database is inherently UNordered - and you won't get any guaranteed order UNLESS you explicitly ask for it - by means of an ORDER BY clause on your outer query.
You need to add the ORDER BY explicitly - like this:
WITH TABLE1 AS
(
.....
)
SELECT
(list of columns ....)
FROM
TABLE1 T1
INNER JOIN
Person P ON P.ID = T1.fkperson
INNER JOIN
Item I ON I.[id] = T1.[fkItem]
ORDER BY
T1.rownum

You are using order by (select null). That means indeterminate ordering. And the order can change from one invocation of the query to another.
You should not be depending on default ordering, even by an external order by. If you want values in a particular order, specify that ordering explicitly in the order by in the windowing clause.

Related

Postgresql - Looping through array_agg

I have a table from which I need to calculate the number of times intent_level changes for each id.
Sample Table format :
id | start_time | intent_level
----+------------+--------------
1 | 2 | status
1 | 3 | status
1 | 1 |
1 | 4 | category
2 | 5 | status
2 | 8 |
2 | 7 | status
I couldn't figure out how to loop through array_agg and compare consecutive elements. Below I tried using array_agg, but then I don't know how to loop through it and compare consecutive elements.
select
id,
array_agg (intent_level ORDER BY start_time)
FROM temp.chats
GROUP BY id;
which gives output :
id | array_agg
----+-----------------------------
1 | {"",status,status,category}
2 | {status,status,""}
Desired output is :
id | array_agg
----+-----------------------------
1 | 2
2 | 1
2 (since value changes from "" to status(1 to 2) and status to category(3 to 4))
1 (since value changes from status to ""(2 to 3))
CREATE AND INSERT QUERIES :
create table temp.chats (
id varchar(5),
start_time varchar(5),
intent_level varchar(20)
);
insert into temp.chats values
('1', '2', 'status'),
('1', '3', 'status'),
('1', '1', ''),
('1', '4', 'category'),
('2', '5', 'status'),
('2', '8', ''),
('2', '7', 'status');
Use lag() and aggregate:
select id, count(*)
from (select c.*,
lag(intent_level) over (partition by id order by start_time) as prev_intent_level
from temp.chats c
) c
where prev_intent_level is distinct from intent_level
group by id;
Here is a db<>fiddle.
Arrays seem quite unnecessary for this.

SQL SELECT multiple count in one query

I have these two tables:
and I want get this result:
How can I achieve this by using only one query?
I tried with join and count and group by but I cannot get it right.
I tried this already, but I cannot get it to work properly.
SELECT
coupon.*,
couponUsers.returned AS COUPON_TOTAL_USERS,
couponUses.returned AS COUPON_TOTAL_USES
FROM
coupon,
(SELECT
coupon.COUPON_CODE,
COUNT(redeemed.REDEEMED_USER) AS returned
FROM
coupon
JOIN
redeemed ON coupon.COUPON_CODE = redeemed.REDEEMED_CODE
GROUP BY
redeemed.REDEEMED_USER) couponUsers,
(SELECT
coupon.COUPON_CODE,
COUNT(redeemed.REDEEMED_CODE) AS returned
FROM
coupon
JOIN
redeemed ON coupon.COUPON_CODE = redeemed.REDEEMED_CODE
GROUP BY
redeemed.REDEEMED_CODE) couponUses
WHERE
coupon.COUPON_CODE = couponUsers.COUPON_CODE
AND coupon.COUPON_CODE = couponUses.COUPON_CODE
GROUP BY
coupon.COUPON_CODE
ORDER BY
coupon.COUPON_ID ASC
This is the build schema if you want to try it yourself in SQL fiddle or something like that..
CREATE TABLE IF NOT EXISTS `coupon`
(
`COUPON_ID` int(11) NOT NULL,
`COUPON_CODE` varchar(32) NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `coupon` (`COUPON_ID`, `COUPON_CODE`) VALUES
(1, "AAAAA"),
(2, "BBBBB"),
(3, "CCCCC"),
(4, "DDDDD"),
(5, "EEEEE");
CREATE TABLE IF NOT EXISTS `redeemed` (
`REDEEMED_ID` int(11) NOT NULL,
`REDEEMED_USER` varchar(32) NOT NULL,
`REDEEMED_CODE` varchar(32) NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `redeemed` (`REDEEMED_ID`, `REDEEMED_USER`, `REDEEMED_CODE`) VALUES
(1, "TOM", "AAAAA"),
(2, "PAULA", "BBBBB"),
(3, "TOBI", "CCCCC"),
(4, "JANA", "DDDDD"),
(5, "INGO", "EEEEE"),
(6, "TOM", "AAAAA"),
(7, "PETER", "EEEEE"),
(8, "JIM", "DDDDD"),
(9, "SARA", "AAAAA"),
(10, "TOBI", "CCCCC"),
(11, "PAULA", "AAAAA"),
(12, "TOM", "AAAAA"),
(13, "PAULA", "BBBBB"),
(14, "JIM", "DDDDD"),
(15, "JANA", "DDDDD");
i am trying this already a couple hours..
its time for some help ^^
You should be able to generate the wanted counts in a single table query:
select
redeemed_code
, count(*) as tot_uses
, count(distinct redeemed_user) as tot_users
from redeemed
group by redeemed_code
You can join this to the coupon table for final output, for example with a left join you would get all coupons listed in coupon even if none have been redeemed yet.
select
c.coupon_id
, c.coupon_code
, coalesce(d.tot_uses,0) as tot_uses
, coalesce(d.tot_users,0) as tot_users
from coupon as c
left join (
select
redeemed_code
, count(*) as tot_uses
, count(distinct redeemed_user) as tot_users
from redeemed
group by redeemed_code
) as d on c.coupon_code = d.redeemed_code
coupon_id
coupon_code
tot_uses
tot_users
1
AAAAA
5
3
2
BBBBB
2
1
3
CCCCC
2
1
4
DDDDD
4
2
5
EEEEE
2
2
db<>fiddle here
This is for PostgreSQL, but this query should work for you. You can do the GROUP BY on the redeemed table and join that with coupon to get the COUPON_ID. \
psql=# WITH redeemed_query AS (
SELECT
"REDEEMED_CODE",
COUNT(*) AS "TOTAL_USES",
COUNT(DISTINCT("REDEEMED_USER")) AS "TOTAL_USERS"
from redeemed GROUP BY "REDEEMED_CODE"
)
SELECT * FROM redeemed_query
INNER JOIN
coupon ON redeemed_query."REDEEMED_CODE" = coupon."COUPON_CODE";
REDEEMED_CODE | TOTAL_USES | TOTAL_USERS | COUPON_ID | COUPON_CODE
---------------+------------+-------------+-----------+-------------
AAAAA | 5 | 3 | 1 | AAAAA
BBBBB | 2 | 1 | 2 | BBBBB
CCCCC | 2 | 1 | 3 | CCCCC
DDDDD | 4 | 2 | 4 | DDDDD
EEEEE | 2 | 2 | 5 | EEEEE
(5 rows)

How can I choose which column do I refer to?

I have 2 tables with some duplicate columns. I need to join them without picking which columns I want to select:
CREATE TABLE IF NOT EXISTS animals (
id int(6) unsigned NOT NULL,
cond varchar(200) NOT NULL,
animal varchar(200) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
INSERT INTO animals (id, cond, animal) VALUES
('1', 'fat', 'cat'),
('2', 'slim', 'cat'),
('3', 'fat', 'dog'),
('4', 'slim', 'dog'),
('5', 'normal', 'dog');
CREATE TABLE IF NOT EXISTS names (
id int(6) unsigned NOT NULL,
name varchar(200) NOT NULL,
animal varchar(200) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
INSERT INTO names (id, name, animal) VALUES
('1', 'LuLu', 'cat'),
('2', 'DoDo', 'cat'),
('3', 'Jack', 'dog'),
('4', 'Shorty', 'dog'),
('5', 'Stinky', 'dog');
SELECT *
FROM animals AS a
JOIN names as n
ON a.id = n.id;
Result:
| id | cond | animal | id | name | animal |
| --- | ------ | ------ | --- | ------ | ------ |
| 1 | fat | cat | 1 | LuLu | cat |
| 2 | slim | cat | 2 | DoDo | cat |
| 3 | fat | dog | 3 | Jack | dog |
| 4 | slim | dog | 4 | Shorty | dog |
| 5 | normal | dog | 5 | Stinky | dog |
But when I try to make another request from the resulting table like:
SELECT name
FROM
(
SELECT *
FROM animals AS a
JOIN names as n
ON a.id = n.id
) as res_tbl
WHERE name = 'LuLu';
I get:
Query Error: Error: ER_DUP_FIELDNAME: Duplicate column name 'id'
Is there any way of avoiding it except removing duplicate columns from the 1st request?
P.S. in fact I am using PostgreSQL, I create my schema as MySQL because I am more used to it
You have columns with the same name in both tables, which causes ambiguity.
If you just want the name column in the outer query, then select that column only in the subquery:
select name
from (
select n.name
from animals a
inner join names n using (id)
) t
where ...
If you want more columns, then you would typically alias the homonym columns to remove the ambiguity - as for the joining column (here, id), the using() syntax is sufficient. So, for example:
select ...
from (
select id, a.cond, a.animal as animal1, n.name, n.animal as animal2
from animals a
inner join names n using (id)
) t
where ...
You may also select the records themselves, instead of the columns from them, which you can then access in an outer query using the usual record.column syntax;
SELECT a.cond animal_cond,
n.name animal_name
FROM (
SELECT a, n
FROM animals AS a
JOIN names as n
ON a.id = n.id
) t

Join clause to return results from both tables SQL Server

Please see: http://sqlfiddle.com/#!18/6552d/4
Tables:
CREATE TABLE Figure(
MonthNumber varchar(10),
Code varchar(10),
Figure int
);
INSERT INTO Figure (MonthNumber, Code, Figure)
VALUES ('5', 'S', '25'),
('5','G', '30'),
('4','K', '30');
CREATE TABLE Person(
Code varchar(10)
);
INSERT INTO Person (Code)
VALUES ('S'),
('G' ),
('K' ),
('D' ),
('J');
Query:
SELECT F.[MonthNumber],
f.[Code],
f.[Figure]
FROM
Figure F
RIGHT JOIN Person P
ON F.[Code] = P.[Code]
WHERE F.[MonthNumber] = '5'
Result:
| MonthNumber | Code | Figure |
|-------------|------|--------|
| 5 | S | 25 |
| 5 | G | 30 |
But i am looking for a result like this:
| MonthNumber | Code | Figure |
|-------------|------|--------|
| 5 | S | 25 |
| 5 | G | 30 |
| 5 | K | 30 |
| 5 | D | 0 |
| 5 | J | 0 |
I want to limit the figures to month 5 but also bring back all people even if they didnt have a figure. I thought using right join syntax would be the correct thing for this but its not giving me the result i want. I cant really work out the logic i need. Any help is appreciated. thanks
An outer join seems right. Why are you using RIGHT JOIN instead of LEFT JOIN? The latter is usually simpler to follow.
SELECT '5' as MonthNumber, p.[Code], COALESCE(f.Figure, 0) as Figure
FROM Person P LEFT JOIN
Figure F
ON F.[Code] = P.[Code] AND F.MonthNumber = '5';
The condition on the second table (in a LEFT JOIN) needs to go in the ON clause.
I think this will do what it should, but not what your asking exactly because of the data:
SELECT F.[MonthNumber], -- this wont select 5 for all rows, but you can hard code this to the 5
P.[Code],
ISNULL(f.[Figure], 0) AS Figure
FROM
Person P
LEFT OUTER JOIN Figure F ON P.[Code] = F.[Code]
AND F.[MonthNumber] = '5'
you can use a query like below.
update fiddle -http://sqlfiddle.com/#!18/6552d/24
SELECT
MonthNumber= ISNULL(MonthNumber,'5'),
P.Code,
Figure= ISNULL(Figure,0)
FROM
Figure F
RIGHT JOIN Person P
ON F.[Code] = P.[Code]
AND F.[MonthNumber] ='5'
Try this I hope it helps
CREATE TABLE #Figure(
MonthNumber varchar(10),
Code varchar(10),
Figure int
);
INSERT INTO #Figure (MonthNumber, Code, Figure)
VALUES ('5', 'S', '25'),
('5','G', '30'),
('4','K', '30');
CREATE TABLE #Person(
Code varchar(10)
);
INSERT INTO #Person (Code)
VALUES ('S'),
('G' ),
('K' ),
('D' ),
('J');
SELECT F.[MonthNumber],
P.[Code],
f.[Figure]
FROM #Person p
LEFT JOIN #Figure F
ON P.[Code] = F.[Code]
WHERE F.[MonthNumber] = '5'
or F.[MonthNumber] is null

Select rows into columns and show a flag in the column

Trying to get an output like the below:
| UserFullName | JAVA | DOTNET | C | HTML5 |
|--------------|--------|--------|--------|--------|
| Anne San | | | | |
| John Khruf | 1 | 1 | | 1 |
| Mary Jane | 1 | | | 1 |
| George Mich | | | | |
This shows the roles of a person. A person could have 0 or N roles. When a person has a role, I am showing a flag, like '1'.
Actually I have 2 blocks of code:
Block #1: The tables and a simple output which generates more than 1 rows per person.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
CREATE TABLE PersonalRoles
(
id int identity primary key,
UserID varchar(100),
RoleID varchar(5),
);
INSERT INTO PersonalRoles
(UserID, RoleID)
VALUES
('John.Khruf', '1'),
('John.Khruf', '2'),
('Mary.Jane', '1'),
('Mary.Jane', '4'),
('John.Khruf', '4');
CREATE TABLE Users
(
UserID varchar(20),
EmployeeType varchar(1),
EmployeeStatus varchar(1),
UserFullName varchar(500),
);
INSERT INTO Users
(UserID, EmployeeType, EmployeeStatus, UserFullName)
VALUES
('John.Khruf', 'E', 'A', 'John Khruf'),
('Mary.Jane', 'E', 'A', 'Mary Jane'),
('Anne.San', 'E', 'A', 'Anne San'),
('George.Mich', 'T', 'A', 'George Mich');
Query 1:
SELECT
A.UserFullName,
B.RoleID
FROM
Users A
LEFT JOIN PersonalRoles B ON B.UserID = A.UserID
WHERE
A.EmployeeStatus = 'A'
ORDER BY
A.EmployeeType ASC,
A.UserFullName ASC
Results:
| UserFullName | RoleID |
|--------------|--------|
| Anne San | (null) |
| John Khruf | 1 |
| John Khruf | 2 |
| John Khruf | 4 |
| Mary Jane | 1 |
| Mary Jane | 4 |
| George Mich | (null) |
Block #2: An attempt to convert the rows into columns to be used in the final result
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
Query 1:
SELECT
*
FROM
(
SELECT CodeID, Description
FROM AvailableRoles
) d
PIVOT
(
MAX(CodeID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Results:
| Java | DOTNET | C | HTML5 |
|--------|--------|-------|--------|
| 1 | 2 | 3 | 4 |
Any help in mixing both blocks to show the top output will be welcome. Thanks.
Another option without PIVOT operator is:
select u.UserFullName,
max(case when a.CodeID='1' then '1' else '' end) JAVA,
max(case when a.CodeID='2' then '1' else '' end) DOTNET,
max(case when a.CodeID='3' then '1' else '' end) C,
max(case when a.CodeID='4' then '1' else '' end) HTML5
from
Users u
LEFT JOIN PersonalRoles p on (u.UserID = p.UserID)
LEFT JOIN AvailableRoles a on (p.RoleID = a.CodeID)
group by u.UserFullName
order by u.UserFullName
SQLFiddle: http://sqlfiddle.com/#!3/630c3/19
You can try this.
SELECT *
FROM
(
select u.userfullname,
case when p.roleid is not null then 1 end as roleid,
a.description
from users u
left join personalroles p
on p.userid = u.userid
left join availableroles a
on a.codeid = p.roleid
) d
PIVOT
(
MAX(roleID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Fiddle