GROUP BY error with TOP in T-SQL / SQL Server - sql-server

I have this table in my SQL Server database.
CREATE TABLE [dbo].[CODIFICHE_FARMACI]
(
[Principio_Attivo] [nvarchar](250) NULL,
[LanguageID] [nvarchar](50) NOT NULL,
[Codice] [nvarchar](50) NOT NULL,
[Confezione_rif] [nvarchar](1000) NULL,
[ATC] [nvarchar](100) NULL,
[Farmaco] [nvarchar](1000) NULL,
[Confezione] [nvarchar](1000) NULL,
[Ditta] [nvarchar](100) NULL,
CONSTRAINT [PK_CODIFICHE_FARMACI]
PRIMARY KEY CLUSTERED ([LanguageID] ASC, [Codice] ASC)
)
Now I want extract from this table the first 60 record group by Farmaco column.
So I wrote this query :
SELECT TOP 60 *
FROM CODIFICHE_FARMACI
GROUP BY Farmaco
But I have this strange error:
La colonna 'CODIFICHE_FARMACI.Principio_Attivo' non è valida nell'elenco di selezione perché non è inclusa né in una funzione di aggregazione né nella clausola GROUP BY.
In English:
The column 'CODIFICHE_FARMACI.Principio_Attivo' is invalid in the select list because it is not included in either an aggregate function or the GROUP BY clause.
EDIT: with this query, I get this result
As you can see I have replicate the column Farmaco (There are two times ABBA, ABESART)
EDIT as result I want :
|FARMACO|
ABBA
ABESART
ABILIFY

If you want to select the first 60 Farmaco values while showing only distinct values, you can try using SELECT DISTINCT:
SELECT DISTINCT TOP 60 Farmaco
FROM [dbo].[CODIFICHE_FARMACI]
ORDER BY Farmaco
Note that if you really have records that are duplicate then it implies your data is not normalized. Possibly, the duplicates only are the same with regard to certain columns, but not others.

SELECT TOP 60 cf.Farmaco
FROM CODIFICHE_FARMACI AS cf
GROUP BY cf.Farmaco
ORDER BY cf.Farmaco
When you use GROUP BY, you'll get a distinct values from the column/s followed by GROUP BY (in your case Farmaco).
First the FROM statement is going be executed, then the retrieved data set now with alias cf from CODIFICHE_FARMACI is going to be grouped by cf.Farmaco.
The SELECT command will retrieve only cf.Farmaco column values, with ORDER BY they will be ordered ascending (because ORDER BY expression, has a default ascending ordering). At the end TOP 60 will filter only 60 ROWS from the data set.
When you specify SELECT * and you have GROUP BY clause you will have a problem, because every column written in GROUP BY must be at the SELECT statement.
You can also add columns that are a result of aggregate functions such as SUM, MIN, MAX and etc. to the SELECT statement.

Try this:
SELECT Top 60
a.Farmaco
FROM [dbo].[CODIFICHE_FARMACI] A
GROUP BY A.Farmaco

Related

How to convert rows into columns

I have a table named Shift and I am defining OffDays in this table using below mentioned columns.
CREATE TABLE [tbl_Shift](
[OffDay1] [nvarchar](25) NOT NULL CONSTRAINT [DF_tbl_Shift_OffDay1] DEFAULT (N'Sunday'),
[IsAlternateOffDay2] [bit] NULL,
[OffDay2] [nvarchar](25) NULL
)
INSERT INTO [tbl_Shift] VALUES ('Sunday', 'True', 'Saturday')
AlternateOffDay is bit so if it is True than OffDay2 can be defined.
I want the result to be shown like below in case I have OffDay2 as Saturday.
Holidays
----------
Sunday
Saturday
I have tried this but the result comes up in 2 columns and 1 row and also it skips 2nd if first is not null but that's not the problem. I just want them to be in 2 rows.
Select DISTINCT ISNULL(OffDay1,OffDay2) from [HRM].[tbl_Shift]
In this case, the simplest solution is to use a UNION (which implicitly does a DISTINCT):
(simplified, just add any WHERE clause as necessary to e.g. ignore OffDay2 where the flag is not set)
SELECT OffDay1 FROM [HRM].[tbl_Shift]
UNION
SELECT OffDay2 FROM [HRM].[tbl_Shift]
Alternatively, you could look into UNPIVOT which is used for switching column values to multiple rows. Something like this:
SELECT DISTINCT TheDay
FROM
(SELECT OffDay1, OffDay2
FROM tbl_Shift) p
UNPIVOT
(TheDay FOR EachDay IN
(OffDay1, OffDay2)
)AS unpvt;

SQL Server 2016 Compare values from multiple columns in multiple rows in single table

USE dev_db
GO
CREATE TABLE T1_VALS
(
[SITE_ID] [int] NULL,
[LATITUDE] [numeric](10, 6) NULL,
[UNIQUE_ID] [int] NULL,
[COLLECT_RANK] [int] NULL,
[CREATED_RANK] [int] NULL,
[UNIQUE_ID_RANK] [int] NULL,
[UPDATE_FLAG] [int] NULL
)
GO
INSERT INTO T1_VALS
(SITE_ID,LATITUDE,UNIQUE_ID,COLLECT_RANK,CREATED_RANK,UNIQUEID_RANK)
VALUES
(207442,40.900470,59664,1,1,1)
(207442,40.900280,61320,1,1,2)
(204314,40.245220,48685,1,2,2)
(204314,40.245910,59977,1,1,1)
(202416,39.449530,9295,1,1,2)
(202416,39.449680,62264,1,1,1)
I generated the COLLECT_RANK and CREATED_RANK columns from two date columns (not shown here) and the UNIQUEID_RANK column from the UNIQUE_ID which is used here.
I used a SELECT OVER clause with ranking function to generate these columns. A _RANK value of 1 means the latest date or greatest UNIQUE_ID value. I thought my solution would be pretty straight forward using these rank values via array and cursor processing but I seem to have painted myself into a corner.
My problem: I need to choose LONGITUDE value and its UNIQUE_ID based upon the following business rules and set the update value, (1), for that record in its UPDATE_FLAG column.
Select the record w/most recent Collection Date (i.e. RANK value = 1) for a given SITE_ID. If multiple records exist w/same Collection Date (i.e. same RANK value), select the record w/most recent Created Date (RANK value =1) for a given SITE_ID. If multiple records exist w/same Created Date, select the record w/highest Unique ID for a given SITE_ID (i.e. RANK value = 1).
Your suggestions would be most appreciated.
I think you can use top and order by:
select top 1 t1.*
from t1_vals
order by collect_rank asc, create_rank, unique_id desc;
If you want this for sites, which might be what your question is asking, then use row_number():
select t1.*
from (select t1.*,
row_number() over (partition by site_id order by collect_rank asc, create_rank, unique_id desc) as seqnum
from t1_vals
) t1
where seqnum = 1;

How can I assign an order value to rows in a table based on a value of frequence contained in a column

I have this table:
CREATE TABLE [dbo].[Phrase] (
[PhraseId] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[English] NVARCHAR (250) NOT NULL,
[Kana] NVARCHAR (MAX) NULL,
[Kanji] NVARCHAR (MAX) NULL,
[FrequencyA] INT NULL,
[OrderA] INT NULL,
PRIMARY KEY CLUSTERED ([PhraseId] ASC),
CONSTRAINT [FK_PhrasePhraseChapter] FOREIGN KEY ([ChapterId]) REFERENCES [dbo].[PhraseChapter] ([PhraseChapterShortId])
);
The Freq column contains a number from 0 to 20,000.
Is there a way that I can populate the [OrderA] column so as to assign a position to each row. Here's an example of what I mean:
PhraseId FrequencyA OrderA
1 100 3
2 50 4
3 50 5
4 201 1
5 200 2
6 10 7
7 50 6
I realize this is rather difficult to explain so please ask any questions if the question is not clear and I will try to clarify. Note that for the case of the row with the Order of 50 I am not concerned about the order for the PhraseId of 2,3 and 7 as long as one of them appears as the 4th, another the 5th and another the 6th. I have about 15,000 rows so slight differences like this will not make any difference.
You can use RANKING functions to get the desired result.
Adding the code here.
SELECT PhraseId, FrequencyA, ROW_NUMBER() OVER(PARTITION BY (SELECT NULL) ORDER BY FrequencyA DESC) AS OrderA
FROM Phrase
ORDER BY PhraseId
Note: I want to highlight that the colunm PhraseId is a UNIQUEIDENTIFIER, which will not show the numbers as integers as shown in the example, instead it will show GUID.
Adding the UPDATE Query.
;WITH PhraseUpdate
AS
(
SELECT PhraseId, FrequencyA, ROW_NUMBER() OVER(PARTITION BY (SELECT NULL) ORDER BY FrequencyA DESC) AS OrderA
FROM Phrase
)
UPDATE p
SET OrderA = pu.OrderA
FROM PhraseUpdate pu
JOIN Phrase p
ON p.PhraseId = pu.PhraseId

Performing a SUM when value could exist in one of a few different columns

Assume there is a simple table:
CREATE TABLE [dbo].[foo](
[foo_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[val1_id] [int] NULL,
[val1_amount] [int] NULL,
[val2_id] [int] NULL,
[val2_amount] [int] NULL,
[val3_id] [int] NULL,
[val3_amount] [int] NULL,
[val4_id] [int] NULL
[val4_amount] [int] NULL,
) ON [PRIMARY]
And there is some other table that is:
CREATE TABLE [dbo].[val](
[val_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[amount] [int] NOT NULL,
) ON [PRIMARY]
The user of the application will select an entry from the table val, and the application will place val_id and amount in val?_id and val?_amount in a non-deterministic manner (yes, I know this isn't good, but I have to deal with it).
Is there a way to produce output that will group by the val_id from the foo table and SUM the amount values from the foo table, given that the val_id/amount may be stored in any of the val?_id/val?_amount columns? Note that if val_id is stored in val1_id, the amount will always be stored in val1_amount within that row, but the same val_id may appear in a different val?_id in a different row (but the amount will be in the corresponding column)
Well, after you smacked the guy that designed this, you can try to UNPIVOT your columns, and then simply SUM the val_amount resultant column. I'm not gonna actually use UNPIVOT, but I'm doing the same with CROSS APPLY:
SELECT x.Val_id, SUM(x.val_amount) Val_Amount
FROM dbo.foo t
CROSS APPLY
(
VALUES
(t.val1_id, t.val1_amount),
(t.val2_id, t.val2_amount),
(t.val3_id, t.val3_amount),
(t.val4_id, t.val4_amount)
) x (Val_id, val_amount)
GROUP BY x.Val_id;
Here is a sqlfiddle with a live demo for you to try.
I did it using the COALESCE function. I'm not clear why I had to do a nested query, but I couldn't get it to take it otherwise:
select id, sum(amount)
FROM
(SELECT COALESCE(val1_id , val2_id , val3_id , val4_id) id,
COALESCE(val1_amount , val2_amount , val3_amount , val4_amount) amount
from foo) coalesced_data
GROUP BY ID
http://sqlfiddle.com/#!3/2ef35/6
Is there a way to produce output that will group by the val_id from the foo table and SUM the amount values from the foo table, given that the val_id/amount may be stored in any of the val?_id/val?_amount columns?
Hell is other people's data. You might do something like this:
create view FOO as
select foo.id, val1_id as valueid, val1_amount as amt from T
union all
select foo.id val2_id as valueid, val2_amount as amt from T
union all
select foo.id val3_id as valueid, val3_amount as amt from T
union all
select foo.id val4_id as valueid, val4_amount as amt from T
If you have correctly stated that the value will only ever appear in one pair of (val_id, val_amount) columns leaving the other 3 pairs blank, then this will work better than the currently accepted answer (#lamak's)
select id = coalesce(val1_id, val2_id, val3_id, val4_id),
val = sum(coalesce(val1_amount, val2_amount, val3_amount, val4_amount))
from foo
group by coalesce(val1_id, val2_id, val3_id, val4_id);
It is easy to understand and even though the COALESCE is repeated, it is really only evaluated once so there is no performance penalty. The query plan is estimated at 50% the cost of the other query.
The currently accepted answer may also produce a phantom NULL row, for example for the data in my SQL fiddle demo that does not contain anything in the 3rd pair (val3_amount).
SQL Fiddle demo

Options for indexing a view with cte

I have a view for which I want to create an Indexed view. After a lot of energy I was able to put the sql query in place for the view and It looks like this -
ALTER VIEW [dbo].[FriendBalances] WITH SCHEMABINDING as
WITH
trans (Amount,PaidBy,PaidFor, Id) AS
(SELECT Amount,userid AS PaidBy, PaidForUsers_FbUserId AS PaidFor, Id FROM dbo.Transactions
FULL JOIN dbo.TransactionUser ON dbo.Transactions.Id = dbo.TransactionUser.TransactionsPaidFor_Id),
bal (PaidBy,PaidFor,Balance) AS
(SELECT PaidBy,PaidFor, SUM( Amount/ transactionCounts.[_count]) AS Balance FROM trans
JOIN (SELECT Id,COUNT(*)AS _count FROM trans GROUP BY Id) AS transactionCounts ON trans.Id = transactionCounts.Id AND trans.PaidBy <> trans.PaidFor
GROUP BY trans.PaidBy,trans.PaidFor )
SELECT ISNULL(bal.PaidBy,bal2.PaidFor)AS PaidBy,ISNULL(bal.PaidFor,bal2.PaidBy)AS PaidFor,
ISNULL( bal.Balance,0)-ISNULL(bal2.Balance,0) AS Balance
FROM bal
left JOIN bal AS bal2 ON bal.PaidBy = bal2.PaidFor AND bal.PaidFor = bal2.Paidby
WHERE ISNULL( bal.Balance,0)>ISNULL(bal2.Balance,0)
Sample Data for FriendBalances View -
PaidBy PaidFor Balance
------ ------- -------
9990 9991 1000
9990 9992 2000
9990 9993 1000
9991 9993 1000
9991 9994 1000
It is mainly a join of 2 tables.
Transactions -
CREATE TABLE [dbo].[Transactions](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL,
[Amount] [float] NOT NULL,
[UserId] [bigint] NOT NULL,
[Remarks] [nvarchar](255) NULL,
[GroupFbGroupId] [bigint] NULL,
CONSTRAINT [PK_Transactions] PRIMARY KEY CLUSTERED
Sample data in Transactions Table -
Id Date Amount UserId Remarks GroupFbGroupId
-- ----------------------- ------ ------ -------------- --------------
1 2001-01-01 00:00:00.000 3000 9990 this is a test NULL
2 2001-01-01 00:00:00.000 3000 9990 this is a test NULL
3 2001-01-01 00:00:00.000 3000 9991 this is a test NULL
TransactionUsers -
CREATE TABLE [dbo].[TransactionUser](
[TransactionsPaidFor_Id] [bigint] NOT NULL,
[PaidForUsers_FbUserId] [bigint] NOT NULL
) ON [PRIMARY]
Sample Data in TransactionUser Table -
TransactionsPaidFor_Id PaidForUsers_FbUserId
---------------------- ---------------------
1 9991
1 9992
1 9993
2 9990
2 9991
2 9992
3 9990
3 9993
3 9994
Now I am not able to create a view because my query contains cte(s). What are the options that I have now?
If cte can be removed, what should be the other option which would help in creating indexed views.
Here is the error message -
Msg 10137, Level 16, State 1, Line 1 Cannot create index on view "ShareBill.Test.Database.dbo.FriendBalances" because it references common table expression "trans". Views referencing common table expressions cannot be indexed. Consider not indexing the view, or removing the common table expression from the view definition.
The concept:
Transaction mainly consists of:
an Amount that was paid
UserId of the User who paid that amount
and some more information which is not important for now.
TransactionUser table is a mapping between a Transaction and a User Table. Essentially a transaction can be shared between multiple persons. So we store that in this table.
So we have transactions where 1 person is paying for it and other are sharing the amount. So if A pays 100$ for B then B would owe 100$ to A. Similarly if B pays 90$ for A then B would owe only $10 to A. Now if A pays 300$ for A,b,c that means B would owe 110$ and C would owe 10$ to A.
So in this particular view we are aggregating the effective amount that has been paid (if any) between 2 users and thus know how much a person owes another person.
Okay, this gives you an indexed view (that needs an additional view on top of to sort out the who-owes-who detail), but it may not satisfy your requirements still.
/* Transactions table, as before, but with handy unique constraint for FK Target */
CREATE TABLE [dbo].[Transactions](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL,
[Amount] [float] NOT NULL,
[UserId] [bigint] NOT NULL,
[Remarks] [nvarchar](255) NULL,
[GroupFbGroupId] [bigint] NULL,
CONSTRAINT [PK_Transactions] PRIMARY KEY CLUSTERED (Id),
constraint UQ_Transactions_XRef UNIQUE (Id,Amount,UserId)
)
Nothing surprising so far, I hope
/* Much expanded TransactionUser table, we'll hide it away and most of the maintenance is automatic */
CREATE TABLE [dbo]._TransactionUser(
[TransactionsPaidFor_Id] int NOT NULL,
[PaidForUsers_FbUserId] [bigint] NOT NULL,
Amount float not null,
PaidByUserId bigint not null,
UserCount int not null,
LowUserID as CASE WHEN [PaidForUsers_FbUserId] < PaidByUserId THEN [PaidForUsers_FbUserId] ELSE PaidByUserId END,
HighUserID as CASE WHEN [PaidForUsers_FbUserId] < PaidByUserId THEN PaidByUserId ELSE [PaidForUsers_FbUserId] END,
PerUserDelta as (Amount/UserCount) * CASE WHEN [PaidForUsers_FbUserId] < PaidByUserId THEN -1 ELSE 1 END,
constraint PK__TransactionUser PRIMARY KEY ([TransactionsPaidFor_Id],[PaidForUsers_FbUserId]),
constraint FK__TransactionUser_Transactions FOREIGN KEY ([TransactionsPaidFor_Id]) references dbo.Transactions,
constraint FK__TransactionUser_Transaction_XRef FOREIGN KEY ([TransactionsPaidFor_Id],Amount,PaidByUserID)
references dbo.Transactions (Id,Amount,UserId) ON UPDATE CASCADE
)
This table now maintains enough information to allow the view to be constructed. The rest of the work we do is to construct/maintain the data in the table. Note that, with the foreign key constraint, we've already ensured that if, say, an amount is changed in the transactions table, everything gets recalculated.
/* View that mimics the original TransactionUser table -
in fact it has the same name so existing code doesn't need to change */
CREATE VIEW dbo.TransactionUser
with schemabinding
as
select
[TransactionsPaidFor_Id],
[PaidForUsers_FbUserId]
from
dbo._TransactionUser
GO
/* Effectively the PK on the original table */
CREATE UNIQUE CLUSTERED INDEX PK_TransactionUser on dbo.TransactionUser ([TransactionsPaidFor_Id],[PaidForUsers_FbUserId])
Anything that's already written to work against TransactionUser will now work against this view, and be none the wiser. Except, they can't insert/update/delete the rows without some help:
/* Now we write the trigger that maintains the underlying table */
CREATE TRIGGER dbo.T_TransactionUser_IUD
ON dbo.TransactionUser
INSTEAD OF INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
/* Every delete affects *every* row for the same transaction
We need to drop the counts on every remaining row, as well as removing the actual rows we're interested in */
WITH DropCounts as (
select TransactionsPaidFor_Id,COUNT(*) as Cnt from deleted group by TransactionsPaidFor_Id
), KeptRows as (
select tu.TransactionsPaidFor_Id,tu.PaidForUsers_FbUserId,UserCount - dc.Cnt as NewCount
from dbo._TransactionUser tu left join deleted d
on tu.TransactionsPaidFor_Id = d.TransactionsPaidFor_Id and
tu.PaidForUsers_FbUserId = d.PaidForUsers_FbUserId
inner join DropCounts dc
on
tu.TransactionsPaidFor_Id = dc.TransactionsPaidFor_Id
where
d.PaidForUsers_FbUserId is null
), ChangeSet as (
select TransactionsPaidFor_Id,PaidForUsers_FbUserId,NewCount,1 as Keep
from KeptRows
union all
select TransactionsPaidFor_Id,PaidForUsers_FbUserId,null,0
from deleted
)
merge into dbo._TransactionUser tu
using ChangeSet cs on tu.TransactionsPaidFor_Id = cs.TransactionsPaidFor_Id and tu.PaidForUsers_FbUserId = cs.PaidForUsers_FbUserId
when matched and cs.Keep = 1 then update set UserCount = cs.NewCount
when matched then delete;
/* Every insert affects *every* row for the same transaction
This is why the indexed view couldn't be generated */
WITH TU as (
select TransactionsPaidFor_Id,PaidForUsers_FbUserId,Amount,PaidByUserId from dbo._TransactionUser
where TransactionsPaidFor_Id in (select TransactionsPaidFor_Id from inserted)
union all
select TransactionsPaidFor_Id,PaidForUsers_FbUserId,Amount,UserId
from inserted i inner join dbo.Transactions t on i.TransactionsPaidFor_Id = t.Id
), CountedTU as (
select TransactionsPaidFor_Id,PaidForUsers_FbUserId,Amount,PaidByUserId,
COUNT(*) OVER (PARTITION BY TransactionsPaidFor_Id) as Cnt
from TU
)
merge into dbo._TransactionUser tu
using CountedTU new on tu.TransactionsPaidFor_Id = new.TransactionsPaidFor_Id and tu.PaidForUsers_FbUserId = new.PaidForUsers_FbUserId
when matched then update set Amount = new.Amount,PaidByUserId = new.PaidByUserId,UserCount = new.Cnt
when not matched then insert
([TransactionsPaidFor_Id],[PaidForUsers_FbUserId],Amount,PaidByUserId,UserCount)
values (new.TransactionsPaidFor_Id,new.PaidForUsers_FbUserId,new.Amount,new.PaidByUserId,new.Cnt);
Now that the underlying table is being maintained, we can finally write the indexed view you wanted in the first place... almost. The issue is that the totals we create may be positive or negative, because we've normalized the transactions so that we can easily sum them:
CREATE VIEW [dbo]._FriendBalances
WITH SCHEMABINDING
as
SELECT
LowUserID,
HighUserID,
SUM(PerUserDelta) as Balance,
COUNT_BIG(*) as Cnt
FROM dbo._TransactionUser
WHERE LowUserID != HighUserID
GROUP BY
LowUserID,
HighUserID
GO
create unique clustered index IX__FriendBalances on dbo._FriendBalances (LowUserID, HighUserID)
So we finally create a view, built on the indexed view above, that if the balance is negative, we flip the person owed, and the person owing around. But it will use the index on the above view, which is most of the work we were seeking to save by having the indexed view:
create view dbo.FriendBalances
as
select
CASE WHEN Balance >= 0 THEN LowUserID ELSE HighUserID END as PaidBy,
CASE WHEN Balance >= 0 THEN HighUserID ELSE LowUserID END as PaidFor,
ABS(Balance) as Balance
from
dbo._FriendBalances WITH (NOEXPAND)
Now, finally, we insert your sample data:
set identity_insert dbo.Transactions on --Ensure we get IDs we know
GO
insert into dbo.Transactions (Id,[Date] , Amount , UserId , Remarks ,GroupFbGroupId)
select 1 ,'2001-01-01T00:00:00.000', 3000, 9990 ,'this is a test', NULL union all
select 2 ,'2001-01-01T00:00:00.000', 3000, 9990 ,'this is a test', NULL union all
select 3 ,'2001-01-01T00:00:00.000', 3000, 9991 ,'this is a test', NULL
GO
set identity_insert dbo.Transactions off
GO
insert into dbo.TransactionUser (TransactionsPaidFor_Id, PaidForUsers_FbUserId)
select 1, 9991 union all
select 1, 9992 union all
select 1, 9993 union all
select 2, 9990 union all
select 2, 9991 union all
select 2, 9992 union all
select 3, 9990 union all
select 3, 9993 union all
select 3, 9994
And query the final view:
select * from dbo.FriendBalances
PaidBy PaidFor Balance
9990 9991 1000
9990 9992 2000
9990 9993 1000
9991 9993 1000
9991 9994 1000
Now, there is additional work we could do, if we were concerned that someone may find a way to dodge the triggers and perform direct changes to the base tables. The first would be yet another indexed view, that will ensure that every row for the same transaction has the same UserCount value. Finally, with a few additional columns, check constraints, FK constraints and more work in the triggers, I think we can ensure that the UserCount is correct - but it may add more overhead than you want.
I can add scripts for these aspects if you want me to - it depends on how restrictive you want/need the database to be.

Resources