Validating a summary count column with the actual records - sql

I have a column in the User table 'total_approved_sales' that contains the count of all sales with status'approved'.
My total_approved_sales column might be off for some users, so I want to list all users who's total_approved_sales doesn't equal the sum from the sales table
i.e. select count(*) from sales where userId=#userId and status='approved'
Table layout looks like:
USER
- total_approved_sales
sales
- userId
- STATUS
How can I query for those users who's counts are off?

joining to an aggregated derived table:
select
u.UserId
, u.total_approved_sales
, a.recount
from user u
left join (
select s.userid, recount = count(*)
from sales s
where s.status = 'approved'
group by s.userid
) a
on u.userid = a.userid
where u.total_approved_sales <> isnull(a.recount,0)
given the following test setup:
create table [user] (userid int, total_approved_sales int);
insert into [user] values (0,0),(1,1),(2,1)
create table sales (userid int, [status] varchar(32))
insert into sales values (1,'approved'),(1,'pending'),(2,'approved'),(2,'approved')
rextester demo: http://rextester.com/TPQZ17719
returns:
+--------+----------------------+---------+
| UserId | total_approved_sales | recount |
+--------+----------------------+---------+
| 2 | 1 | 2 |
+--------+----------------------+---------+

You can achieve this using APPLY operator:
select *
from [user] u
outer apply (select count(*) from sales where userId=u.id and status='approved') sales(cnt)
where u.total_approved_sales <> sales.cnt;

Related

How to merge users in PostgreSQL

I need to make something to merge some users in PGSQL but I think that pgsql don't own the MERGE property. I just want to know how to make two users to be matched like this :
id | name | username | mail
1 | toto | tata | toto.tata#gmail.com
2 | titi | tutu | titi.tutu#gmail.com
Here I want to chose which data I would like I want to say that I want to merge only username from 2 to 1 so the result would be :
id | name | username | mail
1 | toto | tutu | toto.tata#gmail.com
You just need to select all the columns for first id and the column you need with second id will be a subquery in select list. Please check below answer for selecting merged result.
Schema and insert statements:
create table users (id int , name varchar(50), username varchar(50), mail varchar(50));
insert into users values (1 , 'toto' , 'tata' , 'toto.tata#gmail.com');
insert into users values (2 , 'titi' , 'tutu' , 'titi.tutu#gmail.com');
Query:
select id,name,(select username from users where id=2) username,mail from users where id=1
Output:
id
name
username
mail
1
toto
tutu
toto.tata#gmail.com
db<fiddle here
To merge the rows within the table you can first update first row with data from second row then delete the second row. Try this:
Schema and insert statements:
create table users (id int , name varchar(50), username varchar(50), mail varchar(50));
insert into users values (1 , 'toto' , 'tata' , 'toto.tata#gmail.com');
insert into users values (2 , 'titi' , 'tutu' , 'titi.tutu#gmail.com');
Update query:
update users set username=(select username from users where id=2) where id=1;
delete from users where id=2;
Select query:
select * from users
id
name
username
mail
1
toto
tutu
toto.tata#gmail.com
db<fiddle here
You could use aggregation:
select min(id) as id,
max(name) filter (where id = 1) as name,
max(username) filter (where id = 2) as username,
max(mail) filter (where id = 1) as mail
from t
where id in (1, 2);
This assumes that you want to pull particular column values from particular ids.
Or you could use join:
select t1.id, t1.name, t2.username, t1.mail
from t t1 join
t t2
on t1.id = 1 and t2.id = 2;
If you actually want to change the data, use update and delete:
update t t1
set username = t2.username
from t t2
where t1.id = 1 and t2.id = 2;
delete from t
where t.id = 2;
Here is a db<>fiddle.

How can join table with IN() in ON couse?

I have two table
User
id | name | category
1 | test | [2,4]
Category
id | name
1 | first
2 | second
3 | third
4 | fourth
now i need to join this both table and get data like:
name | category
test | second, fourth
i tried like:
select u.name as name, c.name as category
from user
INNER JOIN category on(c.id in (u.category))
but it's not working.
As others have suggested, if you have any control whatsoever over the design of this database, don't store multiple values in user.category, but instead have a bridging table between the two which maps one or more category values to each user record.
However, if you are not in a position to be able to redesign the database, here's a way to get the result you're looking for. First, let's create some test data:
create table [user]
(
id int,
[name] varchar(50),
category varchar(50) -- I'm assuming this is a string type
)
create table category
(
id int,
[name] varchar(50)
)
insert into [user] values
(1,'test','[2,4]'),
(2,'another test','[1,2,4]'),
(3,'more test','[1,3,2,4]')
insert into category values
(1,'first'),
(2,'second'),
(3,'third'),
(4,'fourth');
Then you can use a CTE with split_string to pull apart the individual category values, join them to their names, then recombine them into a single comma-separated value with for xml:
with r as
(
select
u.[name] as username,
cat.id,
cat.[name] as categoryname
from [user] u
outer apply
(
select value from string_split(substring(u.category,2,len(u.category)-2),',')
) c
left join category cat on c.value = cat.id
)
select
r.username,
stuff(
(select ',' + categoryname
from r r2
where r.username = r2.username
order by r2.id
for xml path ('')), 1, 1, '') as categories
from r
group by r.username
which gives the desired output:
/-----------------------------------------\
| username | categories |
|-------------|---------------------------|
|another test | first,second,fourth |
|more test | first,second,third,fourth |
|test | second,fourth |
\-----------------------------------------/
I'm making a couple of assumptions here:
You're using MS SQL Server
The category values always begin with [, end with ] and contain nothing but a comma-delimited string containing value category ids

how to use SQL table rows as columns for another table

I have one table of activities, one table of users, and a third table linking users to activities using foreign keys.
What I'm trying to do is create a results table that will have the activities as columns and the users as rows with the cells being the number of activities of that type the user participated in.
For example, the columns would be
User | Activity A | Activity B | Activity C
And a user who had done each activity three times would result in a row of
John Doe | 3 | 3 | 3
Now, I can do this easily if I manually add a count() call for each activity in the database like:
select
u.name,
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 1),
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 2),
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 3)
from
user u
But this doesn't help me if someone enters an Activity D into the system tomorrow. The report wouldn't show it. How can I use the Activity table's rows as columns?
I did a quick query that might help. This uses the Pivot function, which was mentioned before.
You can run the whole thing, or just skip to the bottom!
-- Temp tables
IF OBJECT_ID('tempdb.dbo.#_tmp') IS NOT NULL DROP TABLE #_tmp
IF OBJECT_ID('tempdb.dbo.#_user') IS NOT NULL DROP TABLE #_user
IF OBJECT_ID('tempdb.dbo.#_activity') IS NOT NULL DROP TABLE #_activity
IF OBJECT_ID('tempdb.dbo.#_useractivity') IS NOT NULL DROP TABLE #_useractivity
-- User table
CREATE TABLE #_user (
[USER_ID] INT IDENTITY(1,1) NOT NULL,
[FIRST_NAME] NVARCHAR(50)
)
INSERT INTO #_user ([FIRST_NAME])
VALUES ('John'), ('Peter'), ('Paul')
-- Activity table
CREATE TABLE #_activity (
[ACTIVITY_ID] INT IDENTITY(1,1) NOT NULL,
[ACTIVITY_NAME] NVARCHAR(255)
)
INSERT INTO #_activity ([ACTIVITY_NAME])
VALUES ('Sailing'), ('Bowling'), ('Hiking')
-- Composite table
CREATE TABLE #_useractivity (
[LOG_ID] INT IDENTITY(1,1) NOT NULL,
[USER_ID] INT,
[ACTIVITY_ID] INT
)
INSERT INTO #_useractivity ([USER_ID], [ACTIVITY_ID])
VALUES (1,1),(1,2),(1,3),(1,3),(2,2),(2,3),(3,1), (3,2),(1,2),(2,1)
-- Main data table.
SELECT USR.FIRST_NAME
, A.ACTIVITY_NAME
INTO #_tmp
FROM #_useractivity AS UA
INNER JOIN #_user AS USR ON USR.USER_ID = UA.USER_ID
INNER JOIN #_activity AS A ON A.ACTIVITY_ID = UA.ACTIVITY_ID
SELECT * FROM #_tmp
-- Use pivot function to get desired results.
DECLARE #_cols AS NVARCHAR(MAX)
DECLARE #_sql AS NVARCHAR(MAX)
SET #_cols = STUFF((SELECT ',' + QUOTENAME(T.ACTIVITY_NAME)
FROM #_tmp AS T
GROUP BY T.ACTIVITY_NAME
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
-- Trick is to add 1 "counter" before pivoting.
set #_sql = '
SELECT Name, ' + #_cols + '
FROM (
SELECT FIRST_NAME AS Name, ACTIVITY_NAME, 1 AS COUNT
FROM #_tmp
) AS SRC
PIVOT (
SUM(COUNT) FOR ACTIVITY_NAME IN (' + #_cols + ')
) p'
EXEC(#_sql)
Main data table:
FIRST_NAME ACTIVITY_NAME
John Sailing
John Bowling
John Hiking
John Hiking
Peter Bowling
Peter Hiking
Paul Sailing
Paul Bowling
John Bowling
Peter Sailing
Output:
Name Bowling Hiking Sailing
John 2 2 1
Paul 1 NULL 1
Peter 1 1 1
You seem to want conditional aggregation:
select u.name,
sum(case when ua.activityID = 1 then 1 else 0 end) as cnt_1,
sum(case when ua.activityID = 2 then 1 else 0 end) as cnt_2,
sum(case when ua.activityID = 3 then 1 else 0 end) as cnt_3
from user u left join
userActivity ua
on ua.userID = u.userID
group by u.name;

SQL count with join

Total novice. Trying this again.
2 Tables Biz and Users
Business has IdNum, created_at, account_type, business_name
Users has IdNum, country, first_name, last_name
Question: How many total businesses are from Japan?
I know I need to use inner join.
I made some assumptions and created this example.
First I created two tables, db_users and db_partners and
inserted some sample data. I am assuming "Users" are
sales managers for the "Partners" and that each partner
is assigned one user. Users can have multiple partners.
It is strange that "Country" is an attribute of Users and
not Partners, but that was how I interpreted the example.
MariaDB [test_time]> create table db_users (
UserID int unsigned not null auto_increment primary key,
UserName varchar(20),
Country varchar(8)
);
MariaDB [test_time]> create table db_partners (
PartnerID int unsigned not null auto_increment primary key,
PartnerName varchar(20),
Created datetime,
Size int unsigned,
UserID int unsigned
);
MariaDB [test_time]> insert into db_users
(UserName,Country)
values
('Abel','CA'),
('Baker','CA'),
('Charlie','JP'),
('Donald','JP'),
('Edgar','JP')
;
MariaDB [test_time]> insert into db_partners
(PartnerName,Created,Size,UserID)
values
('Kraft',now(),45,1),
('Ford',now(),66,2),
('Hortons',now(),22,1),
('Kroger',now(),15,4)
;
Then I selected the partners where the associated user was in CA:
MariaDB [test_time]> select
UserName,PartnerName
from
db_users join db_partners using (UserID)
where
Country='CA'
;
+----------+-------------+
| UserName | PartnerName |
+----------+-------------+
| Abel | Kraft |
| Abel | Hortons |
| Baker | Ford |
+----------+-------------+
3 rows in set (0.00 sec)
MariaDB [test_time]> select
count(Country)
from
db_users join db_partners using (UserID)
where
Country='CA'
;
+----------------+
| count(Country) |
+----------------+
| 3 |
+----------------+
1 row in set (0.00 sec)
I am not sure this is what you wanted. If not, please clarify your
question.
If IdNum is your foreign key (join condition between your tables) just use
SELECT count(*)
FROM Business, Users
WHERE Business.IdNum = Users.IdNum
AND Business.country = 'Japan';
If you wanted the total count of all businesses from Canada (assuming you can join from the ID field in Users to the ID field in Biz), counting each distinct business only once-
select count(distinct u.country) --will only count unique entries once
from Biz b
inner join Users u
on b.ID = u.ID
where u.country = 'Japan'
If you wanted a count of all rows, instead of the unique rows-
select count(u.country) --will count all entries
from Biz b
inner join Users u
on b.ID = u.ID
where u.country = 'Japan'

SQL query assistance with bridge table

I'm working with a existing database and trying to write a sql query to get out all the account information including permission levels. This is for a security audit. We want to dump all of this information out in a readible fashion to make it easy to compare. My problem is that there is a bridge/link table for the permissions so there are multiple records per user. I want to get back results with all the permission for one user on one line. Here is an example:
Table_User:
UserId UserName
1 John
2 Joe
3 James
Table_UserPermissions:
UserId PermissionId Rights
1 10 1
1 11 2
1 12 3
2 11 2
2 12 3
3 10 2
PermissionID links to a table with the name of the Permission and what it does. Right is like 1 = view, 2 = modify, and etc.
What I get back from a basic query for User 1 is:
UserId UserName PermissionId Rights
1 John 10 1
1 John 11 2
1 John 12 3
What I would like something like this:
UserId UserName Permission1 Rights1 Permission2 Right2 Permission3 Right3
1 John 10 1 11 2 12 3
Ideally I would like this for all users.
The closest thing I've found is the Pivot function in SQL Server 2005.
Link
The problem with this from what I can tell is that I need to name each column for each user and I'm not sure how to get the rights level. With real data I have about 130 users and 40 different permissions.
Is there another way with just sql that I can do this?
You could do something like this:
select userid, username
, max(case when permissionid=10 then rights end) as permission10_rights
, max(case when permissionid=11 then rights end) as permission11_rights
, max(case when permissionid=12 then rights end) as permission12_rights
from userpermissions
group by userid, username;
You have to explicitly add a similar max(...) column for each permissionid.
If you where using MySQL I would suggest you use group_concat() like below.
select UserId, UserName,
group_concat(PermissionId) as PermIdList,
group_concat(Rights SEPARATOR ',') as RightsList
from Table_user join Table_UserPermissions on
Table_User.UserId = Table_UserPermissions.UserId=
GROUP BY Table_User.UserId
This would return
UserId UserName PermIdList RightsList
1 John 10,11,12 1,2,3
A quick google search for 'mssql group_concat' revealed a couple different stored procedures (I), (II) for MSSQL that can achieve the same behavior.
Short answer:
No.
You can't dynamically add columns in to your query.
Remember, SQL is a set based language. You query sets and join sets together.
What you're digging out is a recursive list and requiring that the list be strung together horizontally rather then vertically.
You can, sorta, fake it, with a set of self joins, but in order to do that, you have to know all possible permissions before you write the query...which is what the other suggestions have proposed.
You can also pull the recordset back into a different language and then iterate through that to generate the proper columns.
Something like:
SELECT Table_User.userID, userName, permissionid, rights
FROM Table_User
LEFT JOIN Table_UserPermissions ON Table_User.userID =Table_UserPermissions.userID
ORDER BY userName
And then display all the permissions for each user using something like (Python):
userID = recordset[0][0]
userName = recordset[0][1]
for row in recordset:
if userID != row[0]:
printUserPermissions(username, user_permissions)
user_permissions = []
username = row[1]
userID = row[0]
user_permissions.append((row[2], row[3]))
printUserPermissions(username, user_permissions)
You could create a temporary table_flatuserpermissions of:
UserID
PermissionID1
Rights1
PermissionID2
Rights2
...etc to as many permission/right combinations as you need
Insert records to this table from Table_user with all permission & rights fields null.
Update records on this table from table_userpermissions - first record insert and set PermissionID1 & Rights1, Second record for a user update PermissionsID2 & Rights2, etc.
Then you query this table to generate your report.
Personally, I'd just stick with the UserId, UserName, PermissionID, Rights columns you have now.
Maybe substitute in some text for PermissionID and Rights instead of the numeric values.
Maybe sort the table by PermissionID, User instead of User, PermissionID so the auditor could check the users on each permission type.
If it's acceptable, a strategy I've used, both for designing and/or implementation, is to dump the query unpivoted into either Excel or Access. Both have much friendlier UIs for pivoting data, and a lot more people are comfortable in that environment.
Once you have a design you like, then it's easier to think about how to duplicate it in TSQL.
It seems like the pivot function was designed for situations where you can use an aggregate function on one of the fields. Like if I wanted to know how much revenue each sales person made for company x. I could sum up the price field from a sales table. I would then get the sales person and how much revenue in sales they have. For the permissions though it doesn't make sense to sum/count/etc up the permissionId field or the Rights field.
You may want to look at the following example on creating cross-tab queries in SQL:
http://www.databasejournal.com/features/mssql/article.php/3521101/Cross-Tab-reports-in-SQL-Server-2005.htm
It looks like there are new operations that were included as part of SQL Server 2005 called PIVOT and UNPIVOT
For this type of data transformation you will need to perform both an UNPIVOT and then a PIVOT of the data. If you know the values that you want to transform, then you can hard-code the query using a static pivot, otherwise you can use dynamic sql.
Create tables:
CREATE TABLE Table_User
([UserId] int, [UserName] varchar(5))
;
INSERT INTO Table_User
([UserId], [UserName])
VALUES
(1, 'John'),
(2, 'Joe'),
(3, 'James')
;
CREATE TABLE Table_UserPermissions
([UserId] int, [PermissionId] int, [Rights] int)
;
INSERT INTO Table_UserPermissions
([UserId], [PermissionId], [Rights])
VALUES
(1, 10, 1),
(1, 11, 2),
(1, 12, 3),
(2, 11, 2),
(2, 12, 3),
(3, 10, 2)
;
Static PIVOT:
select *
from
(
select userid,
username,
value,
col + '_'+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) src
pivot
(
max(value)
for col in (permissionid_1, rights_1,
permissionid_2, rights_2,
permissionid_3, rights_3)
) piv
order by userid
See SQL Fiddle with Demo
Dynamic PIVOT:
If you have an unknown number of permissionids and rights, then you can use dynamic sql:
DECLARE
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name +'_'+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) t
cross apply sys.columns as C
where C.object_id = object_id('Table_UserPermissions') and
C.name not in ('UserId')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select userid,
username,
value,
col + ''_''+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p
order by userid'
exec(#query)
See SQL Fiddle with demo
The result for both is:
| USERID | USERNAME | PERMISSIONID_1 | RIGHTS_1 | PERMISSIONID_2 | RIGHTS_2 | PERMISSIONID_3 | RIGHTS_3 |
---------------------------------------------------------------------------------------------------------
| 1 | John | 10 | 1 | 11 | 2 | 12 | 3 |
| 2 | Joe | 11 | 2 | 12 | 3 | (null) | (null) |
| 3 | James | 10 | 2 | (null) | (null) | (null) | (null) |